From 58974092a5134af5660f6d7c9896e634190da332 Mon Sep 17 00:00:00 2001 From: jose luis <2064537+sosan@users.noreply.github.com> Date: Sat, 30 Mar 2024 21:17:47 +0100 Subject: [PATCH] add networkmode service: added tests fixNetworkModeToService is responsible for adjusting the network mode of services in docker compose (services:) and generate a mapping of deployments based on the network mode of each service merging containers into the destination deployment, and removing transferred deployments Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> --- pkg/kobject/kobject.go | 1 + pkg/loader/compose/compose.go | 1 + pkg/transformer/kubernetes/k8sutils.go | 108 +++++ pkg/transformer/kubernetes/k8sutils_test.go | 493 ++++++++++++++++++++ pkg/transformer/kubernetes/kubernetes.go | 1 + 5 files changed, 604 insertions(+) diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 4217d663..557f5061 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -118,6 +118,7 @@ type ServiceConfig struct { ReadOnly bool `compose:"read_only"` Args []string `compose:"args"` VolList []string `compose:"volumes"` + NetworkMode string `compose:"network_mode"` Network []string `compose:"network"` Labels map[string]string `compose:"labels"` Annotations map[string]string `compose:""` diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index ab7c3213..69a4618f 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -486,6 +486,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.Kompos serviceConfig.HostName = composeServiceConfig.Hostname serviceConfig.DomainName = composeServiceConfig.DomainName serviceConfig.Secrets = composeServiceConfig.Secrets + serviceConfig.NetworkMode = composeServiceConfig.NetworkMode if composeServiceConfig.StopGracePeriod != nil { serviceConfig.StopGracePeriod = composeServiceConfig.StopGracePeriod.String() diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 890e3455..547fffe1 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -48,6 +48,16 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +const ( + NetworkModeService = "service:" +) + +type DeploymentMapping struct { + SourceDeploymentName string + TargetDeploymentName string + ContainerDestination []api.Container +} + /** * Generate Helm Chart configuration */ @@ -985,3 +995,101 @@ func reformatSecretConfigUnderscoreWithDash(secretConfig types.ServiceSecretConf return newSecretConfig } + +// fixNetworkModeToService is responsible for adjusting the network mode of services in docker compose (services:) +// generate a mapping of deployments based on the network mode of each service +// merging containers into the destination deployment, and removing transferred deployments +func (k *Kubernetes) fixNetworkModeToService(objects *[]runtime.Object, services map[string]kobject.ServiceConfig) { + deploymentMappings := searchNetworkModeToService(services) + if len(deploymentMappings) == 0 { + return + } + mergeContainersIntoDestinationDeployment(deploymentMappings, objects) + removeDeploymentTransfered(deploymentMappings, objects) +} + +// mergeContainersIntoDestinationDeployment takes a list of deployment mappings and a list of runtime objects +// and merges containers from source deployment into the destination deployment +func mergeContainersIntoDestinationDeployment(deploymentMappings []DeploymentMapping, objects *[]runtime.Object) { + for _, currentDeploymentMap := range deploymentMappings { + addContainersFromSourceToTargetDeployment(objects, currentDeploymentMap) + } +} + +// addContainersFromSourceToTargetDeployment adds containers from the source deployment +// if current deployment name matches source deployment name +func addContainersFromSourceToTargetDeployment(objects *[]runtime.Object, currentDeploymentMap DeploymentMapping) { + for _, obj := range *objects { + if deploy, ok := obj.(*appsv1.Deployment); ok { + if deploy.ObjectMeta.Name == currentDeploymentMap.SourceDeploymentName { + addContainersToTargetDeployment(objects, deploy.Spec.Template.Spec.Containers, currentDeploymentMap.TargetDeploymentName) + } + } + } +} + +// addContainersToTargetDeployment takes +// - list of runtime objects +// - list of containers to append +// - deployment name to transfer +// appends the containers to the target deployment if its name matches +func addContainersToTargetDeployment(objects *[]runtime.Object, containersToAppend []api.Container, nameDeploymentToTransfer string) { + for _, obj := range *objects { + if deploy, ok := obj.(*appsv1.Deployment); ok { + if deploy.ObjectMeta.Name == nameDeploymentToTransfer { + deploy.Spec.Template.Spec.Containers = append(deploy.Spec.Template.Spec.Containers, containersToAppend...) + } + } + } +} + +// searchNetworkModeToService iterates over services and checking their network mode service: +// its separates over process of transferring containers, +// it determines where each container should be removed from and where it should be added to +func searchNetworkModeToService(services map[string]kobject.ServiceConfig) (deploymentMappings []DeploymentMapping) { + deploymentMappings = []DeploymentMapping{} + for _, service := range services { + if !strings.Contains(service.NetworkMode, NetworkModeService) { + continue + } + splitted := strings.Split(service.NetworkMode, ":") + if len(splitted) < 2 { + continue + } + deploymentMappings = append(deploymentMappings, DeploymentMapping{ + SourceDeploymentName: service.Name, + TargetDeploymentName: splitted[1], + }) + } + return deploymentMappings +} + +// removeDeploymentTransfered iterates over a list of DeploymentMapping and +// removes each deployment that marked in deploymentMappings +func removeDeploymentTransfered(deploymentMappings []DeploymentMapping, objects *[]runtime.Object) { + for _, currentDeploymentMap := range deploymentMappings { + removeTargetDeployment(objects, currentDeploymentMap.SourceDeploymentName) + } +} + +// removeTargetDeployment iterates over a list of runtime objects +// and removes the target deployment from the list +func removeTargetDeployment(objects *[]runtime.Object, targetDeploymentName string) { + for i := len(*objects) - 1; i >= 0; i-- { + if deploy, ok := (*objects)[i].(*appsv1.Deployment); ok { + if deploy.ObjectMeta.Name == targetDeploymentName { + *objects = removeFromSlice(*objects, (*objects)[i]) + } + } + } +} + +// removeFromSlice removes a specific object from a slice of runtime objects and returns the updated slice +func removeFromSlice(objects []runtime.Object, objectToRemove runtime.Object) []runtime.Object { + for i, currentObject := range objects { + if reflect.DeepEqual(currentObject, objectToRemove) { + return append(objects[:i], objects[i+1:]...) + } + } + return objects +} diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index 637d7f2b..767bc29a 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -29,7 +29,10 @@ import ( "github.com/kubernetes/kompose/pkg/testutils" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" + api "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) /* @@ -738,3 +741,493 @@ func TestRemoveEmptyInterfaces(t *testing.T) { }) } } + +func Test_removeFromSlice(t *testing.T) { + type args struct { + objects []runtime.Object + objectToRemove runtime.Object + } + tests := []struct { + name string + args args + want []runtime.Object + }{ + { + name: "remove object from slice", + args: args{ + objects: []runtime.Object{ + &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, + &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, + &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + objectToRemove: &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, + }, + want: []runtime.Object{ + &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, + &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := removeFromSlice(tt.args.objects, tt.args.objectToRemove); !reflect.DeepEqual(got, tt.want) { + t.Errorf("removeFromSlice() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_removeTargetDeployment(t *testing.T) { + type args struct { + objects *[]runtime.Object + targetDeploymentName string + } + tests := []struct { + name string + args args + want *[]runtime.Object + }{ + { + name: "remove middle object from slice", + args: args{ + objects: &[]runtime.Object{ + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + targetDeploymentName: "remove", + }, + want: &[]runtime.Object{ + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + }, + { + name: "remove 2 objects from slice", + args: args{ + objects: &[]runtime.Object{ + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + targetDeploymentName: "remove", + }, + want: &[]runtime.Object{ + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + }, + { + name: "remove 2 object from slice, only persist last one", + args: args{ + objects: &[]runtime.Object{ + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "remove"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + targetDeploymentName: "remove", + }, + want: &[]runtime.Object{ + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + removeTargetDeployment(tt.args.objects, tt.args.targetDeploymentName) + if !reflect.DeepEqual(tt.args.objects, tt.want) { + t.Errorf("removeFromSlice() = %v, want %v", tt.args.objects, tt.want) + } + }) + } +} + +func Test_removeDeploymentTransfered(t *testing.T) { + type args struct { + deploymentMappings []DeploymentMapping + objects *[]runtime.Object + } + tests := []struct { + name string + args args + want *[]runtime.Object + }{ + { + name: "remove deployment already transferred", + args: args{ + deploymentMappings: []DeploymentMapping{ + { + TargetDeploymentName: "app", + SourceDeploymentName: "db", + ContainerDestination: []corev1.Container{ + { + Name: "app", + }, + }, + }, + }, + objects: &[]runtime.Object{ + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "db"}}, + }, + }, + want: &[]runtime.Object{ + &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "app"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + removeDeploymentTransfered(tt.args.deploymentMappings, tt.args.objects) + if !reflect.DeepEqual(tt.args.objects, tt.want) { + t.Errorf("removeFromSlice() = %v, want %v", tt.args.objects, tt.want) + } + }) + } +} + +func Test_searchNetworkModeToService(t *testing.T) { + tests := []struct { + name string + services map[string]kobject.ServiceConfig + want []DeploymentMapping + }{ + { + name: "search network mode to service", + services: map[string]kobject.ServiceConfig{ + "app": { + Name: "app", + }, + "db": { + Name: "db", + NetworkMode: "service:app", + }, + }, + want: []DeploymentMapping{ + { + SourceDeploymentName: "db", + TargetDeploymentName: "app", + ContainerDestination: nil, + }, + }, + }, + { + name: "error and not set service:app", + services: map[string]kobject.ServiceConfig{ + "app": { + Name: "app", + }, + "db": { + Name: "db", + }, + }, + want: []DeploymentMapping{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotDeploymentMappings := searchNetworkModeToService(tt.services); !reflect.DeepEqual(gotDeploymentMappings, tt.want) { + t.Errorf("searchNetworkModeToService() = %v, want %v", gotDeploymentMappings, tt.want) + } + }) + } +} + +func Test_addContainersToTargetDeployment(t *testing.T) { + k := Kubernetes{} + var objectsK []runtime.Object + + appK, err := k.Transform( + kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": { + ContainerName: "app", + Image: "image", + }, + }, + }, kobject.ConvertOptions{CreateD: true, Replicas: 3}) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + objectsK = append(objectsK, appK...) + + dbK, err := k.Transform( + kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"db": { + ContainerName: "db", + Image: "image", + NetworkMode: "service:app", + }, + }, + }, kobject.ConvertOptions{CreateD: true, Replicas: 3}) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + objectsK = append(objectsK, dbK...) + containersToADD := objectsK[1].(*appsv1.Deployment) + + type args struct { + objects *[]runtime.Object + containersToAppend []api.Container + nameDeploymentToTransfer string + } + tests := []struct { + name string + args args + want int + }{ + { + name: "add one container more to target deployment", + args: args{ + objects: &objectsK, + containersToAppend: containersToADD.Spec.Template.Spec.Containers, + nameDeploymentToTransfer: "app", + }, + want: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + beforeContainers := (*tt.args.objects)[0].(*appsv1.Deployment).Spec.Template.Spec.Containers + if len(beforeContainers) != 1 { + t.Errorf("Expected %d containers, got %d", 1, len(beforeContainers)) + } + + addContainersToTargetDeployment(tt.args.objects, tt.args.containersToAppend, tt.args.nameDeploymentToTransfer) + afterContainers := (*tt.args.objects)[0].(*appsv1.Deployment).Spec.Template.Spec.Containers + if len(afterContainers) != tt.want { + t.Errorf("Expected %d containers, got %d", tt.want, len(afterContainers)) + } + }) + } +} + +func Test_addContainersFromSourceToTargetDeployment(t *testing.T) { + type args struct { + objects *[]runtime.Object + currentDeploymentMap DeploymentMapping + } + tests := []struct { + name string + args args + want int + }{ + { + name: "add one container more to target deployment", + args: args{ + objects: &[]runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "app"}, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "app", + Image: "image", + }, + }, + }, + }, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "db"}, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "db", + Image: "image", + }, + }, + }, + }, + }, + }, + }, + currentDeploymentMap: DeploymentMapping{ + SourceDeploymentName: "db", + TargetDeploymentName: "app", + ContainerDestination: []corev1.Container{ + { + Name: "db", + Image: "image", + }, + }, + }, + }, + want: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + beforeContainers := (*tt.args.objects)[0].(*appsv1.Deployment).Spec.Template.Spec.Containers + if len(beforeContainers) != 1 { + t.Errorf("Expected %d containers, got %d", 1, len(beforeContainers)) + } + + addContainersFromSourceToTargetDeployment(tt.args.objects, tt.args.currentDeploymentMap) + afterContainers := (*tt.args.objects)[0].(*appsv1.Deployment).Spec.Template.Spec.Containers + if len(afterContainers) != tt.want { + t.Errorf("Expected %d containers, got %d", tt.want, len(afterContainers)) + } + }) + } +} + +func Test_mergeContainersIntoDestinationDeployment(t *testing.T) { + type args struct { + deploymentMappings []DeploymentMapping + objects *[]runtime.Object + } + tests := []struct { + name string + args args + want int + }{ + { + name: "merge containers into destination deployment", + args: args{ + objects: &[]runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "app"}, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "app", + Image: "image", + }, + }, + }, + }, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "db"}, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "db", + Image: "image", + }, + }, + }, + }, + }, + }, + }, + deploymentMappings: []DeploymentMapping{ + { + SourceDeploymentName: "db", + TargetDeploymentName: "app", + ContainerDestination: []corev1.Container{ + { + Name: "db", + Image: "image", + }, + }, + }, + }, + }, + want: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + beforeContainers := (*tt.args.objects)[0].(*appsv1.Deployment).Spec.Template.Spec.Containers + if len(beforeContainers) != 1 { + t.Errorf("Expected %d containers, got %d", 1, len(beforeContainers)) + } + mergeContainersIntoDestinationDeployment(tt.args.deploymentMappings, tt.args.objects) + afterContainers := (*tt.args.objects)[0].(*appsv1.Deployment).Spec.Template.Spec.Containers + if len(afterContainers) != tt.want { + t.Errorf("Expected %d containers, got %d", tt.want, len(afterContainers)) + } + }) + } +} + +func TestKubernetes_fixNetworkModeToService(t *testing.T) { + type args struct { + objects *[]runtime.Object + services map[string]kobject.ServiceConfig + } + tests := []struct { + name string + // fields fields + want int + args args + }{ + { + name: "fix network mode to service", + args: args{ + objects: &[]runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "app"}, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "app", + Image: "image", + }, + }, + }, + }, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "db"}, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "db", + Image: "image", + }, + }, + }, + }, + }, + }, + }, + services: map[string]kobject.ServiceConfig{ + "app": { + Name: "app", + Image: "image", + }, + "db": { + Name: "db", + Image: "image", + NetworkMode: "service:app", + }, + }, + }, + want: 2, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := &Kubernetes{} + beforeContainers := (*tt.args.objects)[0].(*appsv1.Deployment).Spec.Template.Spec.Containers + if len(beforeContainers) != 1 { + t.Errorf("Expected %d containers, got %d", 1, len(beforeContainers)) + } + + k.fixNetworkModeToService(tt.args.objects, tt.args.services) + afterContainers := (*tt.args.objects)[0].(*appsv1.Deployment).Spec.Template.Spec.Containers + if len(afterContainers) != tt.want { + t.Errorf("Expected %d containers, got %d", tt.want, len(afterContainers)) + } + }) + } +} diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index 360ebc1d..aec205e9 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -1666,6 +1666,7 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. transformer.AssignNamespaceToObjects(&allobjects, komposeObject.Namespace) } // k.FixWorkloadVersion(&allobjects) + k.fixNetworkModeToService(&allobjects, komposeObject.ServiceConfigs) return allobjects, nil }