From 1f9eb468138a3a0db427e0c7f16d63750fbf18e0 Mon Sep 17 00:00:00 2001 From: Maysun Faisal <31771087+maysunfaisal@users.noreply.github.com> Date: Tue, 22 Aug 2023 20:21:43 -0400 Subject: [PATCH] Provide a Service Port Name (#379) --- pkg/devfile/devfile.go | 22 ++- pkg/devfile/devfile_test.go | 362 +++++++++++++++++++++++++++++++++++- 2 files changed, 382 insertions(+), 2 deletions(-) diff --git a/pkg/devfile/devfile.go b/pkg/devfile/devfile.go index 623fad495..9021604ea 100644 --- a/pkg/devfile/devfile.go +++ b/pkg/devfile/devfile.go @@ -341,20 +341,40 @@ func GetResourceFromDevfile(log logr.Logger, devfileData data.DevfileData, deplo if currentPort > 0 { servicePort := corev1.ServicePort{ + Name: strconv.Itoa(currentPort), Port: int32(currentPort), TargetPort: intstr.FromInt(currentPort), } isPresent := false + portNameMap := make(map[string]bool) for _, port := range resources.Services[0].Spec.Ports { + portNameMap[port.Name] = true if port.Port == servicePort.Port { isPresent = true - break } } if !isPresent { + if portNameMap[servicePort.Name] { + generatedName := fmt.Sprintf("%s-%s", servicePort.Name, util.GetRandomString(4, true)) + portNameMap[generatedName] = true + servicePort.Name = generatedName + } resources.Services[0].Spec.Ports = append(resources.Services[0].Spec.Ports, servicePort) + + for i, port := range resources.Services[0].Spec.Ports { + if port.Name == "" { + // if port name is empty for other service ports, assign a name + // because name is required if there is more than one port + portName := strconv.Itoa(int(port.Port)) + if portNameMap[portName] { + portName = fmt.Sprintf("%s-%s", portName, util.GetRandomString(4, true)) + portNameMap[portName] = true + } + resources.Services[0].Spec.Ports[i].Name = portName + } + } } } } diff --git a/pkg/devfile/devfile_test.go b/pkg/devfile/devfile_test.go index a000be280..d2a5dd0b6 100644 --- a/pkg/devfile/devfile_test.go +++ b/pkg/devfile/devfile_test.go @@ -816,6 +816,133 @@ metadata: name: java-springboot schemaVersion: 2.2.0` + kubernetesInlinedDevfileWithNamedSvcPort := ` +commands: +- apply: + component: image-build + id: build-image +- apply: + component: kubernetes-deploy + id: deployk8s +- composite: + commands: + - build-image + - deployk8s + group: + isDefault: true + kind: deploy + parallel: false + id: deploy +components: +- image: + autoBuild: false + dockerfile: + buildContext: . + rootRequired: false + uri: docker/Dockerfile + imageName: java-springboot-image:latest + name: image-build +- attributes: + api.devfile.io/k8sLikeComponent-originalURI: deploy.yaml + deployment/container-port: 5566 + deployment/containerENV: + - name: FOO + value: foo11 + - name: BAR + value: bar11 + deployment/cpuLimit: "2" + deployment/cpuRequest: 701m + deployment/memoryLimit: 500Mi + deployment/memoryRequest: 401Mi + deployment/replicas: 5 + deployment/route: route111222 + kubernetes: + deployByDefault: false + endpoints: + - name: http-8081 + path: / + secure: false + targetPort: 8081 + inlined: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + creationTimestamp: null + labels: + maysun: test + name: deploy-sample + spec: + replicas: 1 + selector: {} + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app.kubernetes.io/instance: component-sample + spec: + containers: + - env: + - name: FOO + value: foo1 + - name: BARBAR + value: bar1 + image: quay.io/redhat-appstudio/user-workload:application-service-system-component-sample + imagePullPolicy: Always + livenessProbe: + httpGet: + path: / + port: 1111 + initialDelaySeconds: 10 + periodSeconds: 10 + name: container-image + ports: + - containerPort: 1111 + readinessProbe: + initialDelaySeconds: 10 + periodSeconds: 10 + tcpSocket: + port: 1111 + resources: + limits: + cpu: "2" + memory: 500Mi + requests: + cpu: 700m + memory: 400Mi + status: {} + --- + apiVersion: v1 + kind: Service + metadata: + creationTimestamp: null + labels: + app.kubernetes.io/created-by: application-service + app.kubernetes.io/instance: component-sample + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: backend + app.kubernetes.io/part-of: application-sample + maysun: test + name: service-sample + spec: + ports: + - port: 1111 + name: "5566" + targetPort: 1111 + - port: 1112 + targetPort: 1112 + - port: 1113 + name: "1112" + targetPort: 1113 + selector: + app.kubernetes.io/instance: component-sample + status: + loadBalancer: {} + name: kubernetes-deploy +metadata: + name: java-springboot +schemaVersion: 2.2.0` + kubernetesInlinedDevfileIngress := ` commands: - apply: @@ -2094,10 +2221,226 @@ schemaVersion: 2.2.0` Spec: corev1.ServiceSpec{ Ports: []corev1.ServicePort{ { + Name: "1111", Port: int32(1111), TargetPort: intstr.FromInt(1111), }, { + Name: "5566", + Port: int32(5566), + TargetPort: intstr.FromInt(5566), + }, + }, + Selector: map[string]string{ + "app.kubernetes.io/instance": "component-sample", + }, + }, + }, + wantIngress: networkingv1.Ingress{ + TypeMeta: metav1.TypeMeta{ + Kind: "Ingress", + APIVersion: "networking.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "component-sample", + Labels: map[string]string{ + "app.kubernetes.io/created-by": "application-service", + "app.kubernetes.io/instance": "component-sample", + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "component-sample", + "app.kubernetes.io/part-of": "application-sample", + }, + }, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + { + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + PathType: &implementationSpecific, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "component-sample", + Port: networkingv1.ServiceBackendPort{ + Number: 5566, + }, + }, + }, + }, + }, + }, + }, + Host: host, + }, + }, + }, + }, + wantRoute: routev1.Route{ + TypeMeta: metav1.TypeMeta{ + Kind: "Route", + APIVersion: "route.openshift.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "component-sample", + Labels: map[string]string{ + "app.kubernetes.io/created-by": "application-service", + "app.kubernetes.io/instance": "component-sample", + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "component-sample", + "app.kubernetes.io/part-of": "application-sample", + }, + Annotations: map[string]string{}, + }, + Spec: routev1.RouteSpec{ + Host: "route111222", + Path: "/", + Port: &routev1.RoutePort{ + TargetPort: intstr.FromInt(5566), + }, + To: routev1.RouteTargetReference{ + Kind: "Service", + Name: "component-sample", + }, + }, + }, + }, + { + name: "Simple devfile from Inline with named svc port", + devfileString: kubernetesInlinedDevfileWithNamedSvcPort, + componentName: "component-sample", + appName: "application-sample", + image: "image1", + hostname: host, + wantDeploy: appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: "apps/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "component-sample", + Labels: map[string]string{ + "app.kubernetes.io/created-by": "application-service", + "app.kubernetes.io/instance": "component-sample", + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "component-sample", + "app.kubernetes.io/part-of": "application-sample", + "maysun": "test", + }, + }, + Spec: appsv1.DeploymentSpec{ + RevisionHistoryLimit: &revHistoryLimit, + Replicas: &replica, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/instance": "component-sample", + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app.kubernetes.io/instance": "component-sample", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container-image", + Env: []corev1.EnvVar{ + { + Name: "FOO", + Value: "foo11", + }, + { + Name: "BARBAR", + Value: "bar1", + }, + { + Name: "BAR", + Value: "bar11", + }, + }, + Image: "image1", + ImagePullPolicy: corev1.PullAlways, + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/", + Port: intstr.FromInt(5566), + }, + }, + InitialDelaySeconds: int32(10), + PeriodSeconds: int32(10), + }, + Ports: []corev1.ContainerPort{ + { + ContainerPort: int32(1111), + }, + { + ContainerPort: int32(5566), + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt(5566), + }, + }, + InitialDelaySeconds: int32(10), + PeriodSeconds: int32(10), + }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("2"), + corev1.ResourceMemory: resource.MustParse("500Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("701m"), + corev1.ResourceMemory: resource.MustParse("401Mi"), + }, + }, + }, + }, + }, + }, + }, + }, + wantService: corev1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "component-sample", + Labels: map[string]string{ + "app.kubernetes.io/created-by": "application-service", + "app.kubernetes.io/instance": "component-sample", + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "component-sample", + "app.kubernetes.io/part-of": "application-sample", + "maysun": "test", + }, + }, + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "5566", + Port: int32(1111), + TargetPort: intstr.FromInt(1111), + }, + { + Name: "1112", + Port: int32(1112), + TargetPort: intstr.FromInt(1112), + }, + { + Name: "1112", + Port: int32(1113), + TargetPort: intstr.FromInt(1113), + }, + { + Name: "5566", Port: int32(5566), TargetPort: intstr.FromInt(5566), }, @@ -3371,7 +3714,24 @@ schemaVersion: 2.2.0` } if len(actualResources.Services) > 0 { - assert.Equal(t, tt.wantService, actualResources.Services[0], "First Service did not match") + if tt.name == "Simple devfile from Inline with named svc port" { + assert.Equal(t, tt.wantService.TypeMeta, actualResources.Services[0].TypeMeta, "First Service TypeMeta did not match") + assert.Equal(t, tt.wantService.ObjectMeta, actualResources.Services[0].ObjectMeta, "First Service ObjectMeta did not match") + for _, port := range actualResources.Services[0].Spec.Ports { + matched := false + for _, wantPort := range tt.wantService.Spec.Ports { + if port.Port == wantPort.Port && (port.Name == wantPort.Name || strings.Contains(port.Name, wantPort.Name)) { + matched = true + } + } + if !matched { + t.Errorf("Unable to match the Svc port %v with the wanted Svc port", port.Port) + } + } + assert.Equal(t, tt.wantService.Spec.Selector, actualResources.Services[0].Spec.Selector, "First Service Spec.Selector did not match") + } else { + assert.Equal(t, tt.wantService, actualResources.Services[0], "First Service did not match") + } } if len(actualResources.Ingresses) > 0 {