From aeaca72df65f5841473f4f1ac8925242496aa95a Mon Sep 17 00:00:00 2001 From: Ben Meier Date: Thu, 1 Feb 2024 11:45:47 +0000 Subject: [PATCH] feat: added support for multiple containers in the same network --- internal/compose/convert.go | 78 ++++++++++++++++++++------------ internal/compose/convert_test.go | 52 +++++++++++++++++++++ 2 files changed, 102 insertions(+), 28 deletions(-) diff --git a/internal/compose/convert.go b/internal/compose/convert.go index 1663fa8..84dc904 100644 --- a/internal/compose/convert.go +++ b/internal/compose/convert.go @@ -10,6 +10,7 @@ package compose import ( "errors" "fmt" + "slices" "sort" compose "github.com/compose-spec/compose-go/types" @@ -23,29 +24,51 @@ func ConvertSpec(spec *score.WorkloadSpec) (*compose.Project, ExternalVariables, return nil, nil, fmt.Errorf("preparing context: %w", err) } - for _, cSpec := range spec.Containers { + if len(spec.Containers) == 0 { + return nil, nil, errors.New("workload does not have any containers to convert into a compose service") + } + + var project = compose.Project{ + Services: make(compose.Services, 0, len(spec.Containers)), + } + + externalVars := ExternalVariables(ctx.ListEnvVars()) + + var ports []compose.ServicePortConfig + if len(spec.Service.Ports) > 0 { + ports = []compose.ServicePortConfig{} + for _, pSpec := range spec.Service.Ports { + var pubPort = fmt.Sprintf("%v", pSpec.Port) + var tgtPort = pSpec.TargetPort + if pSpec.TargetPort == 0 { + tgtPort = pSpec.Port + } + ports = append(ports, compose.ServicePortConfig{ + Published: pubPort, + Target: uint32(tgtPort), + Protocol: pSpec.Protocol, + }) + } + } + + // When multiple containers are specified we need to identify one container as the "main" container which will own + // the network and use the native workload name. All other containers in this workload will have the container + // name appended as a suffix. We use the natural sort order of the container names and pick the first one + containerNames := make([]string, 0, len(spec.Containers)) + for name := range spec.Containers { + containerNames = append(containerNames, name) + } + slices.Sort(containerNames) + + for _, containerName := range containerNames { + cSpec := spec.Containers[containerName] + var env = make(compose.MappingWithEquals, len(cSpec.Variables)) for key, val := range cSpec.Variables { var envVarVal = ctx.Substitute(val) env[key] = &envVarVal } - var ports []compose.ServicePortConfig - if len(spec.Service.Ports) > 0 { - ports = []compose.ServicePortConfig{} - for _, pSpec := range spec.Service.Ports { - var pubPort = fmt.Sprintf("%v", pSpec.Port) - var tgtPort = pSpec.TargetPort - if pSpec.TargetPort == 0 { - tgtPort = pSpec.Port - } - ports = append(ports, compose.ServicePortConfig{ - Published: pubPort, - Target: uint32(tgtPort), - Protocol: pSpec.Protocol, - }) - } - } // NOTE: Sorting is necessary for DeepEqual call within our Unit Tests to work reliably sort.Slice(ports, func(i, j int) bool { return ports[i].Published < ports[j].Published @@ -83,18 +106,17 @@ func ConvertSpec(spec *score.WorkloadSpec) (*compose.Project, ExternalVariables, Volumes: volumes, } - var proj = compose.Project{ - Services: compose.Services{ - svc, - }, + if len(spec.Containers) > 1 { + // if we have more than 1 container then namespace them with the container name + svc.Name += "-" + containerName + // if we are not the "first" service, then inherit the network from the first service + if len(project.Services) > 0 { + svc.Ports = nil + svc.NetworkMode = "service:" + project.Services[0].Name + } } - var externalVars = ExternalVariables(ctx.ListEnvVars()) - - // NOTE: Only one container per workload can be defined for compose. - // All other containers will be ignored by this tool. - return &proj, externalVars, nil + project.Services = append(project.Services, svc) } - - return nil, nil, errors.New("workload does not have any containers to convert into a compose service") + return &project, externalVars, nil } diff --git a/internal/compose/convert_test.go b/internal/compose/convert_test.go index 5122074..1e61f7b 100644 --- a/internal/compose/convert_test.go +++ b/internal/compose/convert_test.go @@ -163,6 +163,58 @@ func TestScoreConvert(t *testing.T) { "APP_DB_NAME": "", }, }, + { + Name: "Should support multiple containers", + Source: &score.WorkloadSpec{ + Metadata: score.WorkloadMeta{ + Name: "test", + }, + Containers: score.ContainersSpecs{ + "frontend": score.ContainerSpec{ + Image: "busybox", + Variables: map[string]string{ + "PORT": "80", + }, + }, + "backend": score.ContainerSpec{ + Image: "busybox", + Variables: map[string]string{ + "PORT": "81", + }, + }, + }, + Service: score.ServiceSpec{ + Ports: map[string]score.ServicePortSpec{ + "frontend": {Port: 8080, TargetPort: 80}, + "backend": {Port: 8081, TargetPort: 81}, + }, + }, + }, + Project: &compose.Project{ + Services: compose.Services{ + { + Name: "test-backend", + Image: "busybox", + Environment: compose.MappingWithEquals{ + "PORT": stringPtr("81"), + }, + Ports: []compose.ServicePortConfig{ + {Target: 80, Published: "8080"}, + {Target: 81, Published: "8081"}, + }, + }, + { + Name: "test-frontend", + Image: "busybox", + Environment: compose.MappingWithEquals{ + "PORT": stringPtr("80"), + }, + NetworkMode: "service:test-backend", + }, + }, + }, + Vars: ExternalVariables{}, + }, // Errors handling //