From bdff0c4c7813122751a0c48d46b2806d115c7b9b Mon Sep 17 00:00:00 2001 From: Lucas Roesler Date: Sat, 26 Feb 2022 16:34:40 +0100 Subject: [PATCH] fix: return full status object from the info endpoint Ensure that all fields of the `FunctionStatus` are populated based on the function `Deployment`. In particular, this change ensures that the env variables, constrains, and the read-only root filesystem values are populuated correctly. Signed-off-by: Lucas Roesler --- pkg/k8s/function_status.go | 47 +++++++++++++++----- pkg/k8s/function_status_test.go | 79 ++++++++++++++++++++++++++++++++- 2 files changed, 115 insertions(+), 11 deletions(-) diff --git a/pkg/k8s/function_status.go b/pkg/k8s/function_status.go index dd5ff2e08..29b080b7d 100644 --- a/pkg/k8s/function_status.go +++ b/pkg/k8s/function_status.go @@ -23,16 +23,19 @@ func AsFunctionStatus(item appsv1.Deployment) *types.FunctionStatus { labels := item.Spec.Template.Labels function := types.FunctionStatus{ - Name: item.Name, - Replicas: replicas, - Image: functionContainer.Image, - AvailableReplicas: uint64(item.Status.AvailableReplicas), - InvocationCount: 0, - Labels: &labels, - Annotations: &item.Spec.Template.Annotations, - Namespace: item.Namespace, - Secrets: ReadFunctionSecretsSpec(item), - CreatedAt: item.CreationTimestamp.Time, + Name: item.Name, + Replicas: replicas, + Image: functionContainer.Image, + AvailableReplicas: uint64(item.Status.AvailableReplicas), + InvocationCount: 0, + Labels: &labels, + Annotations: &item.Spec.Template.Annotations, + Namespace: item.Namespace, + Secrets: ReadFunctionSecretsSpec(item), + CreatedAt: item.CreationTimestamp.Time, + Constraints: nodeSelectorToConstraint(item), + ReadOnlyRootFilesystem: hasReadOnlyRootFilesystem(item), + EnvVars: map[string]string{}, } req := &types.FunctionResources{Memory: functionContainer.Resources.Requests.Memory().String(), CPU: functionContainer.Resources.Requests.Cpu().String()} @@ -48,8 +51,32 @@ func AsFunctionStatus(item appsv1.Deployment) *types.FunctionStatus { for _, v := range functionContainer.Env { if EnvProcessName == v.Name { function.EnvProcess = v.Value + continue } + function.EnvVars[v.Name] = v.Value } return &function } + +func nodeSelectorToConstraint(deploy appsv1.Deployment) []string { + nodeSelector := deploy.Spec.Template.Spec.NodeSelector + constraints := []string{} + for k, v := range nodeSelector { + constraints = append(constraints, k+"="+v) + } + return constraints +} + +func hasReadOnlyRootFilesystem(function appsv1.Deployment) bool { + securityContext := function.Spec.Template.Spec.Containers[0].SecurityContext + if securityContext == nil { + return false + } + + if securityContext.ReadOnlyRootFilesystem == nil { + return false + } + + return *securityContext.ReadOnlyRootFilesystem +} diff --git a/pkg/k8s/function_status_test.go b/pkg/k8s/function_status_test.go index fda4b14c8..4eb674b9a 100644 --- a/pkg/k8s/function_status_test.go +++ b/pkg/k8s/function_status_test.go @@ -10,6 +10,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -23,7 +24,10 @@ func Test_AsFunctionStatus(t *testing.T) { annotations := map[string]string{"data": "datavalue"} namespace := "func-namespace" envProcess := "process string here" + envVars := map[string]string{"env1": "env1value", "env2": "env2value"} secrets := []string{"0-imagepullsecret", "1-genericsecret", "2-genericsecret"} + readOnlyRootFilesystem := false + constraints := []string{"node-label=node-value"} deploy := appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ @@ -39,6 +43,9 @@ func Test_AsFunctionStatus(t *testing.T) { Labels: labels, }, Spec: corev1.PodSpec{ + NodeSelector: map[string]string{ + "node-label": "node-value", + }, ImagePullSecrets: []corev1.LocalObjectReference{ {Name: "0-imagepullsecret"}, }, @@ -48,7 +55,8 @@ func Test_AsFunctionStatus(t *testing.T) { Image: image, Env: []corev1.EnvVar{ {Name: "fprocess", Value: envProcess}, - {Name: "customEnv", Value: "customValue"}, + {Name: "env1", Value: "env1value"}, + {Name: "env2", Value: "env2value"}, }, }, }, @@ -122,4 +130,73 @@ func Test_AsFunctionStatus(t *testing.T) { t.Errorf("incorrect Secrets: expected %v, got : %v", secrets, status.Secrets) } + if !reflect.DeepEqual(status.EnvVars, envVars) { + t.Errorf("incorrect EnvVars: expected %+v, got %+v", envVars, status.EnvVars) + } + + if !reflect.DeepEqual(status.Constraints, constraints) { + t.Errorf("incorrect Constraints: expected %+v, got %+v", constraints, status.Constraints) + } + + if status.ReadOnlyRootFilesystem != readOnlyRootFilesystem { + t.Errorf("incorrect ReadOnlyRootFilesystem: expected %v, got : %v", readOnlyRootFilesystem, status.ReadOnlyRootFilesystem) + } + + t.Run("can parse readonly root filesystem when nil", func(t *testing.T) { + readOnlyRootFilesystem := false + deployment := deploy + deployment.Spec.Template.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{} + + status := AsFunctionStatus(deployment) + if status.ReadOnlyRootFilesystem != readOnlyRootFilesystem { + t.Errorf("incorrect ReadOnlyRootFilesystem: expected %v, got : %v", readOnlyRootFilesystem, status.ReadOnlyRootFilesystem) + } + }) + + t.Run("can parse readonly root filesystem enabled", func(t *testing.T) { + readOnlyRootFilesystem := true + deployment := deploy + deployment.Spec.Template.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{ + ReadOnlyRootFilesystem: &readOnlyRootFilesystem, + } + + status := AsFunctionStatus(deployment) + if status.ReadOnlyRootFilesystem != readOnlyRootFilesystem { + t.Errorf("incorrect ReadOnlyRootFilesystem: expected %v, got : %v", readOnlyRootFilesystem, status.ReadOnlyRootFilesystem) + } + }) + + t.Run("returns non-empty resource requests", func(t *testing.T) { + deployment := deploy + deployment.Spec.Template.Spec.Containers[0].Resources.Requests = corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("100Mi"), + } + + status := AsFunctionStatus(deployment) + if status.Requests.CPU != "100m" { + t.Errorf("incorrect Requests.CPU: expected %s, got %s", "100m", status.Requests.CPU) + } + + if status.Requests.Memory != "100Mi" { + t.Errorf("incorrect Requests.Memory: expected %s, got %s", "100Mi", status.Requests.Memory) + } + }) + + t.Run("returns non-empty resource limits", func(t *testing.T) { + deployment := deploy + deployment.Spec.Template.Spec.Containers[0].Resources.Limits = corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + corev1.ResourceMemory: resource.MustParse("100Mi"), + } + + status := AsFunctionStatus(deployment) + if status.Limits.CPU != "100m" { + t.Errorf("incorrect Limits.CPU: expected %s, got %s", "100m", status.Limits.CPU) + } + + if status.Limits.Memory != "100Mi" { + t.Errorf("incorrect Limits.Memory: expected %s, got %s", "100Mi", status.Limits.Memory) + } + }) }