diff --git a/config/crd/bases/operator.knative.dev_knativeeventings.yaml b/config/crd/bases/operator.knative.dev_knativeeventings.yaml index c9f44f946b..6f0180bc29 100644 --- a/config/crd/bases/operator.knative.dev_knativeeventings.yaml +++ b/config/crd/bases/operator.knative.dev_knativeeventings.yaml @@ -1142,6 +1142,20 @@ spec: type: object type: object type: array + namespace: + description: A field of namespace name to override the labels and annotations + type: object + properties: + labels: + additionalProperties: + type: string + description: Labels overrides labels for the namespace and its template. + type: object + annotations: + additionalProperties: + type: string + description: Annotations overrides labels for the namespace and its template. + type: object deployments: description: A mapping of deployment name to override type: array diff --git a/config/crd/bases/operator.knative.dev_knativeservings.yaml b/config/crd/bases/operator.knative.dev_knativeservings.yaml index 53753eb9bc..512d39e073 100644 --- a/config/crd/bases/operator.knative.dev_knativeservings.yaml +++ b/config/crd/bases/operator.knative.dev_knativeservings.yaml @@ -1153,6 +1153,20 @@ spec: type: object type: object type: array + namespace: + description: A field of namespace name to override the labels and annotations + type: object + properties: + labels: + additionalProperties: + type: string + description: Labels overrides labels for the namespace and its template. + type: object + annotations: + additionalProperties: + type: string + description: Annotations overrides labels for the namespace and its template. + type: object deployments: description: A mapping of deployment name to override type: array diff --git a/pkg/apis/operator/base/common.go b/pkg/apis/operator/base/common.go index 2b04645c7f..249cc84286 100644 --- a/pkg/apis/operator/base/common.go +++ b/pkg/apis/operator/base/common.go @@ -65,6 +65,9 @@ type KComponentSpec interface { // GetAdditionalManifests gets the list of additional manifests, which should be installed GetAdditionalManifests() []Manifest + // GetNamespaceConfiguration gets the labels and annotations for the namespace + GetNamespaceConfiguration() *NamespaceConfiguration + // GetHighAvailability returns means to set the number of desired replicas GetHighAvailability() *HighAvailability @@ -142,6 +145,10 @@ type CommonSpec struct { // +optional DeploymentOverride []WorkloadOverride `json:"deployments,omitempty"` + // NamespaceConfiguration overrides namespace configurations such as labels and annotations. + // +optional + NamespaceConfiguration *NamespaceConfiguration `json:"namespace,omitempty"` + // Workloads overrides workloads configurations such as resources and replicas. // +optional Workloads []WorkloadOverride `json:"workloads,omitempty"` @@ -191,6 +198,11 @@ func (c *CommonSpec) GetVersion() string { return c.Version } +// GetNamespaceConfiguration implements KComponentSpec. +func (c *CommonSpec) GetNamespaceConfiguration() *NamespaceConfiguration { + return c.NamespaceConfiguration +} + // GetManifests implements KComponentSpec. func (c *CommonSpec) GetManifests() []Manifest { return c.Manifests @@ -247,6 +259,17 @@ type Registry struct { ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` } +// NamespaceConfiguration defines the configurations of namespaces to override. +type NamespaceConfiguration struct { + // Labels overrides labels for the namespace and its template. + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // Annotations overrides labels for the namespace and its template. + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // WorkloadOverride defines the configurations of deployments to override. type WorkloadOverride struct { // Name is the name of the deployment to override. diff --git a/pkg/reconciler/common/namespace.go b/pkg/reconciler/common/namespace.go new file mode 100644 index 0000000000..84abdbe0f9 --- /dev/null +++ b/pkg/reconciler/common/namespace.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + mf "github.com/manifestival/manifestival" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/kubernetes/scheme" + + "knative.dev/operator/pkg/apis/operator/base" +) + +// NamespaceConfigurationTransform mutates the only namespace available for knative serving or eventing +// by changing the labels and annotations. +func NamespaceConfigurationTransform(namespaceConfiguration *base.NamespaceConfiguration) mf.Transformer { + return func(u *unstructured.Unstructured) error { + if u.GetKind() != "Namespace" || namespaceConfiguration == nil { + return nil + } + namespace := &corev1.Namespace{} + err := scheme.Scheme.Convert(u, namespace, nil) + if err != nil { + return err + } + + // Override the labels for the namespace + if namespace.GetLabels() == nil { + namespace.Labels = map[string]string{} + } + + for key, val := range namespaceConfiguration.Labels { + namespace.Labels[key] = val + } + + // Override the annotations for the namespace + if namespace.GetAnnotations() == nil { + namespace.Annotations = map[string]string{} + } + + for key, val := range namespaceConfiguration.Annotations { + namespace.Annotations[key] = val + } + + err = scheme.Scheme.Convert(namespace, u, nil) + if err != nil { + return err + } + return nil + } +} diff --git a/pkg/reconciler/common/namespace_test.go b/pkg/reconciler/common/namespace_test.go new file mode 100644 index 0000000000..ad8107d2dd --- /dev/null +++ b/pkg/reconciler/common/namespace_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2024 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/kubernetes/scheme" + + "knative.dev/operator/pkg/apis/operator/base" +) + +func TestNamespaceConfigurationTransform(t *testing.T) { + tests := []struct { + name string + namespace *corev1.Namespace + override *base.NamespaceConfiguration + expLabels map[string]string + expAnnotations map[string]string + }{{ + name: "Label override", + namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "knative-serving", + Labels: map[string]string{ + "istio-injection": "enabled", + "serving.knative.dev/release": "v0.13.0", + }, + }, + }, + override: &base.NamespaceConfiguration{ + Labels: map[string]string{"a": "b"}, + }, + expLabels: map[string]string{"a": "b", "istio-injection": "enabled", "serving.knative.dev/release": "v0.13.0"}, + expAnnotations: nil, + }, { + name: "Annotation override", + namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "knative-serving", + Labels: map[string]string{ + "istio-injection": "enabled", + "serving.knative.dev/release": "v0.13.0", + }, + }, + }, + override: &base.NamespaceConfiguration{ + Annotations: map[string]string{"c": "d"}, + }, + expLabels: map[string]string{"istio-injection": "enabled", "serving.knative.dev/release": "v0.13.0"}, + expAnnotations: map[string]string{"c": "d"}, + }, { + name: "No override", + namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "knative-serving", + Labels: map[string]string{ + "istio-injection": "enabled", + "serving.knative.dev/release": "v0.13.0", + }, + }, + }, + override: nil, + expLabels: map[string]string{"serving.knative.dev/release": "v0.13.0", "istio-injection": "enabled"}, + expAnnotations: nil, + }, { + name: "Override both labels and annotations", + namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "knative-serving", + }, + }, + override: &base.NamespaceConfiguration{ + Labels: map[string]string{"c1": "d1", "j": "k"}, + Annotations: map[string]string{"c": "d", "x": "y"}, + }, + expLabels: map[string]string{"j": "k", "c1": "d1"}, + expAnnotations: map[string]string{"x": "y", "c": "d"}, + }} + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + u := &unstructured.Unstructured{} + err := scheme.Scheme.Convert(test.namespace, u, nil) + if err != nil { + t.Fatalf("Failed to convert namespace to unstructured: %v", err) + } + NamespaceConfigurationTransform(test.override)(u) + got := &corev1.Namespace{} + if err = scheme.Scheme.Convert(u, got, nil); err != nil { + t.Fatalf("Failed to convert unstructured to namespace: %v", err) + } + + if diff := cmp.Diff(got.GetLabels(), test.expLabels); diff != "" { + t.Fatalf("Unexpected labels: %v", diff) + } + + if diff := cmp.Diff(got.GetAnnotations(), test.expAnnotations); diff != "" { + t.Fatalf("Unexpected annotations: %v", diff) + } + }) + } +} diff --git a/pkg/reconciler/common/transformers.go b/pkg/reconciler/common/transformers.go index 34ec3a1d81..9ca49aeb4d 100644 --- a/pkg/reconciler/common/transformers.go +++ b/pkg/reconciler/common/transformers.go @@ -30,6 +30,7 @@ func transformers(ctx context.Context, obj base.KComponent) []mf.Transformer { return []mf.Transformer{ injectOwner(obj), mf.InjectNamespace(obj.GetNamespace()), + NamespaceConfigurationTransform(obj.GetSpec().GetNamespaceConfiguration()), HighAvailabilityTransform(obj), ImageTransform(obj.GetSpec().GetRegistry(), logger), JobTransform(obj),