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>
This commit is contained in:
jose luis 2024-03-30 21:17:47 +01:00
parent 14152d4a81
commit 58974092a5
No known key found for this signature in database
GPG Key ID: 6D23FAD11F88081A
5 changed files with 604 additions and 0 deletions

View File

@ -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:""`

View File

@ -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()

View File

@ -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
}

View File

@ -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))
}
})
}
}

View File

@ -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
}