diff --git a/api/bases/operator.openstack.org_openstacks.yaml b/api/bases/operator.openstack.org_openstacks.yaml index 2009dd660..24a7c0fc4 100644 --- a/api/bases/operator.openstack.org_openstacks.yaml +++ b/api/bases/operator.openstack.org_openstacks.yaml @@ -39,6 +39,72 @@ spec: properties: controllerManager: properties: + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array resources: properties: claims: diff --git a/api/operator/v1beta1/openstack_types.go b/api/operator/v1beta1/openstack_types.go index 9fd1508c4..76b321f5b 100644 --- a/api/operator/v1beta1/openstack_types.go +++ b/api/operator/v1beta1/openstack_types.go @@ -238,6 +238,10 @@ type ContainerSpec struct { // Resources - Compute Resources for the service operator controller manager // https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ Resources corev1.ResourceRequirements `json:"resources,omitempty"` + + // +kubebuilder:validation:Optional + // Env - Environment variables for the container + Env []corev1.EnvVar `json:"env,omitempty"` } // OpenStackStatus defines the observed state of OpenStack diff --git a/api/operator/v1beta1/zz_generated.deepcopy.go b/api/operator/v1beta1/zz_generated.deepcopy.go index 64cc6b57c..d16554aa9 100644 --- a/api/operator/v1beta1/zz_generated.deepcopy.go +++ b/api/operator/v1beta1/zz_generated.deepcopy.go @@ -30,6 +30,13 @@ import ( func (in *ContainerSpec) DeepCopyInto(out *ContainerSpec) { *out = *in in.Resources.DeepCopyInto(&out.Resources) + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerSpec. diff --git a/bindata/operator/rabbit.yaml b/bindata/operator/rabbit.yaml index a1d4ee3e1..5901dafa4 100644 --- a/bindata/operator/rabbit.yaml +++ b/bindata/operator/rabbit.yaml @@ -29,7 +29,26 @@ spec: fieldPath: metadata.namespace {{- range .RabbitmqOperator.Deployment.Manager.Env }} - name: '{{ .Name }}' +{{- if .Value }} value: '{{ .Value }}' +{{- end }} +{{- if .ValueFrom }} + valueFrom: +{{- if .ValueFrom.FieldRef }} + fieldRef: + fieldPath: '{{ .ValueFrom.FieldRef.FieldPath }}' +{{- end }} +{{- if .ValueFrom.ConfigMapKeyRef }} + configMapKeyRef: + name: '{{ .ValueFrom.ConfigMapKeyRef.Name }}' + key: '{{ .ValueFrom.ConfigMapKeyRef.Key }}' +{{- end }} +{{- if .ValueFrom.SecretKeyRef }} + secretKeyRef: + name: '{{ .ValueFrom.SecretKeyRef.Name }}' + key: '{{ .ValueFrom.SecretKeyRef.Key }}' +{{- end }} +{{- end }} {{- end }} image: {{ .RabbitmqOperator.Deployment.Manager.Image }} name: operator diff --git a/config/crd/bases/operator.openstack.org_openstacks.yaml b/config/crd/bases/operator.openstack.org_openstacks.yaml index 2009dd660..24a7c0fc4 100644 --- a/config/crd/bases/operator.openstack.org_openstacks.yaml +++ b/config/crd/bases/operator.openstack.org_openstacks.yaml @@ -39,6 +39,72 @@ spec: properties: controllerManager: properties: + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array resources: properties: claims: diff --git a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml index e2a3e6bea..a1bee6c67 100644 --- a/config/manifests/bases/openstack-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/openstack-operator.clusterserviceversion.yaml @@ -436,6 +436,13 @@ spec: - description: TLS - overrides tls parameters for public endpoint displayName: TLS path: telemetry.aodhApiOverride.tls + - description: CloudKittyAPIOverride, provides the ability to override the generated + manifest of several child resources. + displayName: Cloud Kitty APIOverride + path: telemetry.cloudKittyApiOverride + - description: TLS - overrides tls parameters for public endpoint + displayName: TLS + path: telemetry.cloudKittyApiOverride.tls - description: Enabled - Whether OpenStack Telemetry services should be deployed and managed displayName: Enabled diff --git a/config/operator/rabbit.yaml b/config/operator/rabbit.yaml index a1d4ee3e1..5901dafa4 100644 --- a/config/operator/rabbit.yaml +++ b/config/operator/rabbit.yaml @@ -29,7 +29,26 @@ spec: fieldPath: metadata.namespace {{- range .RabbitmqOperator.Deployment.Manager.Env }} - name: '{{ .Name }}' +{{- if .Value }} value: '{{ .Value }}' +{{- end }} +{{- if .ValueFrom }} + valueFrom: +{{- if .ValueFrom.FieldRef }} + fieldRef: + fieldPath: '{{ .ValueFrom.FieldRef.FieldPath }}' +{{- end }} +{{- if .ValueFrom.ConfigMapKeyRef }} + configMapKeyRef: + name: '{{ .ValueFrom.ConfigMapKeyRef.Name }}' + key: '{{ .ValueFrom.ConfigMapKeyRef.Key }}' +{{- end }} +{{- if .ValueFrom.SecretKeyRef }} + secretKeyRef: + name: '{{ .ValueFrom.SecretKeyRef.Name }}' + key: '{{ .ValueFrom.SecretKeyRef.Key }}' +{{- end }} +{{- end }} {{- end }} image: {{ .RabbitmqOperator.Deployment.Manager.Image }} name: operator diff --git a/internal/operator/override.go b/internal/operator/override.go index c7330dd9b..ee85e94f9 100644 --- a/internal/operator/override.go +++ b/internal/operator/override.go @@ -112,11 +112,42 @@ func SetOverrides(opOvr operatorv1beta1.OperatorSpec, op *Operator) { op.Deployment.Manager.Resources.Requests.Memory = opOvr.ControllerManager.Resources.Requests.Memory().String() } } + if len(opOvr.ControllerManager.Env) > 0 { + op.Deployment.Manager.Env = mergeEnvVars(op.Deployment.Manager.Env, opOvr.ControllerManager.Env) + } if len(opOvr.Tolerations) > 0 { op.Deployment.Tolerations = mergeTolerations(op.Deployment.Tolerations, opOvr.Tolerations) } } +// mergeEnvVars merges custom environment variables with default environment variables. +// If a custom env var has the same name as a default one, it overrides the default. +// Otherwise, the custom env var is added to the list. +func mergeEnvVars(defaults, custom []corev1.EnvVar) []corev1.EnvVar { + if len(custom) == 0 { + return defaults + } + + // Start with a copy of defaults + merged := make([]corev1.EnvVar, len(defaults)) + copy(merged, defaults) + + // For each custom env var, check if it should override a default one + for _, customEnv := range custom { + f := func(c corev1.EnvVar) bool { + return c.Name == customEnv.Name + } + idx := slices.IndexFunc(merged, f) + if idx >= 0 { + merged[idx] = customEnv + } else { + merged = append(merged, customEnv) + } + } + + return merged +} + // mergeTolerations merges custom tolerations with default tolerations. // If a custom toleration has the same key as a default one, it overrides the default. // Otherwise, the custom toleration is added to the list. diff --git a/internal/operator/override_test.go b/internal/operator/override_test.go index 7f118114d..ad625af82 100644 --- a/internal/operator/override_test.go +++ b/internal/operator/override_test.go @@ -619,6 +619,286 @@ func TestMergeTolerations(t *testing.T) { } } +// --- Test for mergeEnvVars function --- + +func TestMergeEnvVars(t *testing.T) { + defaultEnvVars := []corev1.EnvVar{ + { + Name: "OPERATOR_NAMESPACE", + Value: "default-namespace", + }, + { + Name: "LOG_LEVEL", + Value: "info", + }, + } + + testCases := []struct { + name string + defaults []corev1.EnvVar + custom []corev1.EnvVar + expected []corev1.EnvVar + }{ + { + name: "Empty custom env vars should return defaults", + defaults: defaultEnvVars, + custom: []corev1.EnvVar{}, + expected: defaultEnvVars, + }, + { + name: "Nil custom env vars should return defaults", + defaults: defaultEnvVars, + custom: nil, + expected: defaultEnvVars, + }, + { + name: "Add new env var to defaults", + defaults: defaultEnvVars, + custom: []corev1.EnvVar{ + { + Name: "OPERATOR_SCOPE_NAMESPACE", + Value: "namespace1,namespace2", + }, + }, + expected: []corev1.EnvVar{ + { + Name: "OPERATOR_NAMESPACE", + Value: "default-namespace", + }, + { + Name: "LOG_LEVEL", + Value: "info", + }, + { + Name: "OPERATOR_SCOPE_NAMESPACE", + Value: "namespace1,namespace2", + }, + }, + }, + { + name: "Override existing env var", + defaults: defaultEnvVars, + custom: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "debug", + }, + }, + expected: []corev1.EnvVar{ + { + Name: "OPERATOR_NAMESPACE", + Value: "default-namespace", + }, + { + Name: "LOG_LEVEL", + Value: "debug", // Overridden + }, + }, + }, + { + name: "Mixed: override one, add one", + defaults: defaultEnvVars, + custom: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "debug", + }, + { + Name: "OPERATOR_SCOPE_NAMESPACE", + Value: "namespace1,namespace2,namespace3", + }, + }, + expected: []corev1.EnvVar{ + { + Name: "OPERATOR_NAMESPACE", + Value: "default-namespace", + }, + { + Name: "LOG_LEVEL", + Value: "debug", // Overridden + }, + { + Name: "OPERATOR_SCOPE_NAMESPACE", + Value: "namespace1,namespace2,namespace3", // Added + }, + }, + }, + { + name: "Custom env var with valueFrom", + defaults: defaultEnvVars, + custom: []corev1.EnvVar{ + { + Name: "SECRET_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + Key: "key", + }, + }, + }, + }, + expected: []corev1.EnvVar{ + { + Name: "OPERATOR_NAMESPACE", + Value: "default-namespace", + }, + { + Name: "LOG_LEVEL", + Value: "info", + }, + { + Name: "SECRET_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "my-secret", + }, + Key: "key", + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := mergeEnvVars(tc.defaults, tc.custom) + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("mergeEnvVars() failed:\n got: %+v\nwant: %+v", result, tc.expected) + } + }) + } +} + +// --- Test for environment variables in SetOverrides --- + +func TestEnvVarsOverride(t *testing.T) { + testCases := []struct { + name string + operatorSpec operatorv1beta1.OperatorSpec + initialEnvVars []corev1.EnvVar + expectedEnvVars []corev1.EnvVar + }{ + { + name: "Add env vars to empty list", + operatorSpec: operatorv1beta1.OperatorSpec{ + Name: "rabbitmq-cluster", + ControllerManager: operatorv1beta1.ContainerSpec{ + Env: []corev1.EnvVar{ + { + Name: "OPERATOR_SCOPE_NAMESPACE", + Value: "namespace1,namespace2", + }, + }, + }, + }, + initialEnvVars: nil, + expectedEnvVars: []corev1.EnvVar{ + { + Name: "OPERATOR_SCOPE_NAMESPACE", + Value: "namespace1,namespace2", + }, + }, + }, + { + name: "No custom env vars, keep defaults unchanged", + operatorSpec: operatorv1beta1.OperatorSpec{ + Name: "rabbitmq-cluster", + }, + initialEnvVars: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "info", + }, + }, + expectedEnvVars: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "info", + }, + }, + }, + { + name: "Merge custom env vars with defaults", + operatorSpec: operatorv1beta1.OperatorSpec{ + Name: "rabbitmq-cluster", + ControllerManager: operatorv1beta1.ContainerSpec{ + Env: []corev1.EnvVar{ + { + Name: "OPERATOR_SCOPE_NAMESPACE", + Value: "namespace1,namespace2", + }, + }, + }, + }, + initialEnvVars: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "info", + }, + }, + expectedEnvVars: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "info", + }, + { + Name: "OPERATOR_SCOPE_NAMESPACE", + Value: "namespace1,namespace2", + }, + }, + }, + { + name: "Override default env var", + operatorSpec: operatorv1beta1.OperatorSpec{ + Name: "rabbitmq-cluster", + ControllerManager: operatorv1beta1.ContainerSpec{ + Env: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "debug", + }, + }, + }, + }, + initialEnvVars: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "info", + }, + }, + expectedEnvVars: []corev1.EnvVar{ + { + Name: "LOG_LEVEL", + Value: "debug", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + op := &Operator{ + Name: tc.operatorSpec.Name, + Deployment: Deployment{ + Manager: Container{ + Env: tc.initialEnvVars, + }, + }, + } + + SetOverrides(tc.operatorSpec, op) + + if !reflect.DeepEqual(op.Deployment.Manager.Env, tc.expectedEnvVars) { + t.Errorf("wrong env vars after override:\n got: %+v\nwant: %+v", op.Deployment.Manager.Env, tc.expectedEnvVars) + } + }) + } +} + // --- Test for global defaults initialization --- func TestGlobalTolerationsDefaults(t *testing.T) {