diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 18c431eb89..2fa0316261 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -19,6 +19,7 @@ package kobject import ( "path/filepath" "strconv" + "strings" "time" "github.com/compose-spec/compose-go/v2/types" @@ -27,6 +28,7 @@ import ( "github.com/spf13/cast" v1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" + api "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -349,3 +351,62 @@ func (s *ServiceConfig) GetOSUpdateStrategy() *deployapi.RollingDeploymentStrate return nil } + +func (s *ServiceConfig) CheckServiceType() error { + svcType := s.ServiceType + typeFromLabel := s.DeployLabels["kompose.service.type"] + if len(svcType) != 0 { + return nil + } else if len(typeFromLabel) != 0 { + _, err := handleServiceType(typeFromLabel) + if err != nil { + return err + } + return nil + } + return nil +} + +func (s *ServiceConfig) GetServiceType() string { + svcType := s.ServiceType + typeFromLabel := s.DeployLabels["kompose.service.type"] + if len(svcType) != 0 { + return svcType + } else if len(typeFromLabel) != 0 { + res, err := handleServiceType(typeFromLabel) + if err != nil { + return "" + } + return res + } + return "" +} + +func (s *ServiceConfig) GetServiceNodePort() int32 { + port := s.NodePortPort + var portFromLabel int32 + if labelPort, ok := s.DeployLabels["kompose.service.nodeport.port"]; ok { + portFromLabel = cast.ToInt32(labelPort) + } + if port != 0 { + return port + } else if portFromLabel != 0 { + return portFromLabel + } + return 0 +} + +func handleServiceType(ServiceType string) (string, error) { + switch strings.ToLower(ServiceType) { + case "", "clusterip": + return string(api.ServiceTypeClusterIP), nil + case "nodeport": + return string(api.ServiceTypeNodePort), nil + case "loadbalancer": + return string(api.ServiceTypeLoadBalancer), nil + case "headless": + return "Headless", nil + default: + return "", errors.New("Unknown value " + ServiceType + " , supported values are 'nodeport, clusterip, headless or loadbalancer'") + } +} diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 8242163002..77ac27d02f 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -432,7 +432,7 @@ func (k *Kubernetes) initSvcObject(name string, service kobject.ServiceConfig, p svc.Spec.Selector = transformer.ConfigLabels(service.Name) svc.Spec.Ports = ports - svc.Spec.Type = api.ServiceType(service.ServiceType) + svc.Spec.Type = api.ServiceType(service.GetServiceType()) // Configure annotations annotations := transformer.ConfigAnnotations(service) @@ -458,17 +458,18 @@ func (k *Kubernetes) CreateLBService(name string, service kobject.ServiceConfig) // CreateService creates a k8s service func (k *Kubernetes) CreateService(name string, service kobject.ServiceConfig) *api.Service { + serviceType := service.GetServiceType() svc := k.InitSvc(name, service) // Configure the service ports. servicePorts := k.ConfigServicePorts(service) svc.Spec.Ports = servicePorts - if service.ServiceType == "Headless" { + if serviceType == "Headless" { svc.Spec.Type = api.ServiceTypeClusterIP svc.Spec.ClusterIP = "None" } else { - svc.Spec.Type = api.ServiceType(service.ServiceType) + svc.Spec.Type = api.ServiceType(serviceType) } // Configure annotations diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index c8b137a1d7..019148f696 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -329,6 +329,98 @@ func TestCreateServiceWithServiceUser(t *testing.T) { } } +/* +Test the creation of a service with service type NodePort. +The expected result is that Kompose will set user in PodSpec +*/ +func TestCreateServiceWithServiceTypeNodePort(t *testing.T) { + // An example service + service := kobject.ServiceConfig{ + ContainerName: "name", + Image: "image", + Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, + Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, + Command: []string{"cmd"}, + WorkingDir: "dir", + Args: []string{"arg1", "arg2"}, + VolList: []string{"/tmp/volume"}, + Network: []string{"network1", "network2"}, // not supported + DeployLabels: map[string]string{"kompose.service.type": "nodeport", "kompose.service": "my-service"}, + Annotations: map[string]string{"kompose.service.type": "nodeport"}, + CPUQuota: 1, // not supported + CapAdd: []string{"cap_add"}, // not supported + CapDrop: []string{"cap_drop"}, // not supported + Expose: []string{"expose"}, // not supported + Privileged: true, + Restart: "always", + User: "1234:5678", + } + + komposeObject := kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, + } + k := Kubernetes{} + + objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + + for _, obj := range objects { + if svc, ok := obj.(*corev1.Service); ok { + if svc.Spec.Type != corev1.ServiceTypeNodePort { + t.Errorf("Service type is not NodePort") + } + } + } +} + +/* +Test the creation of a service with service type LoadBalancer. +The expected result is that Kompose will set user in PodSpec +*/ +func TestCreateServiceWithServiceTypeLoadBalancer(t *testing.T) { + // An example service + service := kobject.ServiceConfig{ + ContainerName: "name", + Image: "image", + Environment: []kobject.EnvVar{{Name: "env", Value: "value"}}, + Port: []kobject.Ports{{HostPort: 123, ContainerPort: 456, Protocol: string(corev1.ProtocolTCP)}}, + Command: []string{"cmd"}, + WorkingDir: "dir", + Args: []string{"arg1", "arg2"}, + VolList: []string{"/tmp/volume"}, + Network: []string{"network1", "network2"}, // not supported + DeployLabels: map[string]string{"kompose.service.type": "loadbalancer", "kompose.service": "my-service"}, + Annotations: map[string]string{"kompose.service.type": "loadbalancer"}, + CPUQuota: 1, // not supported + CapAdd: []string{"cap_add"}, // not supported + CapDrop: []string{"cap_drop"}, // not supported + Expose: []string{"expose"}, // not supported + Privileged: true, + Restart: "always", + User: "1234:5678", + } + + komposeObject := kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, + } + k := Kubernetes{} + + objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + + for _, obj := range objects { + if svc, ok := obj.(*corev1.Service); ok { + if svc.Spec.Type != corev1.ServiceTypeLoadBalancer { + t.Errorf("Service type is not NodePort") + } + } + } +} + func TestCreateServiceWithConfigLongSyntax(t *testing.T) { content := "setting: true" target := "/etc/config.yaml" diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index 66b44b4b8b..58649991ac 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -752,6 +752,7 @@ func (k *Kubernetes) ConfigServicePorts(service kobject.ServiceConfig) []api.Ser servicePorts := []api.ServicePort{} seenPorts := make(map[int]struct{}, len(service.Port)) + serviceType := service.GetServiceType() var servicePort api.ServicePort for _, port := range service.Port { if port.HostPort == 0 { @@ -766,7 +767,7 @@ func (k *Kubernetes) ConfigServicePorts(service kobject.ServiceConfig) []api.Ser name := strconv.Itoa(int(port.HostPort)) if _, ok := seenPorts[int(port.HostPort)]; ok { // https://github.com/kubernetes/kubernetes/issues/2995 - if service.ServiceType == string(api.ServiceTypeLoadBalancer) { + if serviceType == string(api.ServiceTypeLoadBalancer) { log.Fatalf("Service %s of type LoadBalancer cannot use TCP and UDP for the same port", name) } name = fmt.Sprintf("%s-%s", name, strings.ToLower(port.Protocol)) @@ -778,8 +779,9 @@ func (k *Kubernetes) ConfigServicePorts(service kobject.ServiceConfig) []api.Ser TargetPort: targetPort, } - if service.ServiceType == string(api.ServiceTypeNodePort) && service.NodePortPort != 0 { - servicePort.NodePort = service.NodePortPort + nodePortPort := service.GetServiceNodePort() + if serviceType == string(api.ServiceTypeNodePort) && nodePortPort != 0 { + servicePort.NodePort = nodePortPort } // If the default is already TCP, no need to include protocol. @@ -1491,9 +1493,14 @@ func buildServiceImage(opt kobject.ConvertOptions, service kobject.ServiceConfig return nil } -func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.ServiceConfig, name string, objects *[]runtime.Object) { +func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.ServiceConfig, name string, objects *[]runtime.Object) error { + err := service.CheckServiceType() + if err != nil { + return err + } + svcType := service.GetServiceType() if k.PortsExist(service) { - if service.ServiceType == "LoadBalancer" { + if svcType == "LoadBalancer" { svcs := k.CreateLBService(name, service) for _, svc := range svcs { svc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(service.ServiceExternalTrafficPolicy) @@ -1513,7 +1520,7 @@ func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.Servi } } } else { - if service.ServiceType == "Headless" { + if svcType == "Headless" { svc := k.CreateHeadlessService(name, service) *objects = append(*objects, svc) if service.ServiceExternalTrafficPolicy != "" { @@ -1523,6 +1530,7 @@ func (k *Kubernetes) configKubeServiceAndIngressForService(service kobject.Servi log.Warnf("Service %q won't be created because 'ports' is not specified", service.Name) } } + return nil } func (k *Kubernetes) configNetworkPolicyForService(service kobject.ServiceConfig, name string, objects *[]runtime.Object) error { @@ -1610,7 +1618,9 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. } // override.. objects = append(objects, k.CreateWorkloadAndConfigMapObjects(groupName, service, opt)...) - k.configKubeServiceAndIngressForService(service, groupName, &objects) + if err := k.configKubeServiceAndIngressForService(service, groupName, &objects); err != nil { + return nil, err + } // Configure the container volumes. volumesMount, volumes, pvc, cms, err := k.ConfigVolumes(groupName, service) @@ -1706,7 +1716,9 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. if opt.Controller == StatefulStateController { service.ServiceType = "Headless" } - k.configKubeServiceAndIngressForService(service, name, &objects) + if err := k.configKubeServiceAndIngressForService(service, name, &objects); err != nil { + return nil, err + } err := k.UpdateKubernetesObjects(name, service, opt, &objects) if err != nil { return nil, errors.Wrap(err, "Error transforming Kubernetes objects") diff --git a/pkg/transformer/openshift/openshift.go b/pkg/transformer/openshift/openshift.go index 2947de4e1a..74e687b60a 100644 --- a/pkg/transformer/openshift/openshift.go +++ b/pkg/transformer/openshift/openshift.go @@ -408,6 +408,9 @@ func (o *OpenShift) Transform(komposeObject kobject.KomposeObject, opt kobject.C log.Warningf("Create multiple service to avoid using mixed protocol in the same service when it's loadbalancer type") } } else { + if err = service.CheckServiceType(); err != nil { + return nil, errors.Wrap(err, "CheckServiceType failed") + } svc := o.CreateService(name, service) objects = append(objects, svc)