diff --git a/api/v1beta1/cryostat_types.go b/api/v1beta1/cryostat_types.go index 1bdbb34a..f4f1f368 100644 --- a/api/v1beta1/cryostat_types.go +++ b/api/v1beta1/cryostat_types.go @@ -88,6 +88,32 @@ type CryostatSpec struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Credentials Database Options" JmxCredentialsDatabaseOptions *JmxCredentialsDatabaseOptions `json:"jmxCredentialsDatabaseOptions,omitempty"` + // Options to configure the Cryostat deployments and pods metadata + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Operand metadata" + OperandMetadata *OperandMetadata `json:"operandMetadata,omitempty"` +} + +type OperandMetadata struct { + // Options to configure the Cryostat deployments metadata + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Deployments metadata" + DeploymentMetadata *ResourceMetadata `json:"deploymentMetadata,omitempty"` + // Options to configure the Cryostat pods metadata + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Pods metadata" + PodMetadata *ResourceMetadata `json:"podMetadata,omitempty"` +} + +type ResourceMetadata struct { + // Annotations to add to the resources during its creation. + // +optional + Annotations map[string]string `json:"annotations,omitempty"` + // Labels to add to the resources during its creation. + // The labels with keys "app" and "component" are reserved + // for use by the operator. + // +optional + Labels map[string]string `json:"labels,omitempty"` } type ResourceConfigList struct { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 8876502c..910b11dd 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -352,6 +352,11 @@ func (in *CryostatSpec) DeepCopyInto(out *CryostatSpec) { *out = new(JmxCredentialsDatabaseOptions) (*in).DeepCopyInto(*out) } + if in.OperandMetadata != nil { + in, out := &in.OperandMetadata, &out.OperandMetadata + *out = new(OperandMetadata) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CryostatSpec. @@ -521,6 +526,31 @@ func (in *NetworkConfigurationList) DeepCopy() *NetworkConfigurationList { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OperandMetadata) DeepCopyInto(out *OperandMetadata) { + *out = *in + if in.DeploymentMetadata != nil { + in, out := &in.DeploymentMetadata, &out.DeploymentMetadata + *out = new(ResourceMetadata) + (*in).DeepCopyInto(*out) + } + if in.PodMetadata != nil { + in, out := &in.PodMetadata, &out.PodMetadata + *out = new(ResourceMetadata) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OperandMetadata. +func (in *OperandMetadata) DeepCopy() *OperandMetadata { + if in == nil { + return nil + } + out := new(OperandMetadata) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PersistentVolumeClaimConfig) DeepCopyInto(out *PersistentVolumeClaimConfig) { *out = *in @@ -645,6 +675,35 @@ func (in *ResourceConfigList) DeepCopy() *ResourceConfigList { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceMetadata) DeepCopyInto(out *ResourceMetadata) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceMetadata. +func (in *ResourceMetadata) DeepCopy() *ResourceMetadata { + if in == nil { + return nil + } + out := new(ResourceMetadata) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SchedulingConfiguration) DeepCopyInto(out *SchedulingConfiguration) { *out = *in diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index da6c3615..83c52ad3 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -54,7 +54,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:2.4.0-dev - createdAt: "2023-09-18T15:10:18Z" + createdAt: "2023-09-28T14:25:20Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -256,6 +256,15 @@ spec: label with key "app" is reserved for use by the operator. displayName: Labels path: networkOptions.grafanaConfig.labels + - description: Options to configure the Cryostat deployments and pods metadata + displayName: Operand metadata + path: operandMetadata + - description: Options to configure the Cryostat deployments metadata + displayName: Deployments metadata + path: operandMetadata.deploymentMetadata + - description: Options to configure the Cryostat pods metadata + displayName: Pods metadata + path: operandMetadata.podMetadata - description: Options to configure Cryostat Automated Report Analysis. displayName: Report Options path: reportOptions @@ -639,6 +648,15 @@ spec: label with key "app" is reserved for use by the operator. displayName: Labels path: networkOptions.grafanaConfig.labels + - description: Options to configure the Cryostat deployments and pods metadata + displayName: Operand metadata + path: operandMetadata + - description: Options to configure the Cryostat deployments metadata + displayName: Deployments metadata + path: operandMetadata.deploymentMetadata + - description: Options to configure the Cryostat pods metadata + displayName: Pods metadata + path: operandMetadata.podMetadata - description: Options to configure Cryostat Automated Report Analysis. displayName: Report Options path: reportOptions diff --git a/bundle/manifests/operator.cryostat.io_clustercryostats.yaml b/bundle/manifests/operator.cryostat.io_clustercryostats.yaml index d4a5a520..9d32a187 100644 --- a/bundle/manifests/operator.cryostat.io_clustercryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_clustercryostats.yaml @@ -1078,6 +1078,45 @@ spec: type: object type: object type: object + operandMetadata: + description: Options to configure the Cryostat deployments and pods + metadata + properties: + deploymentMetadata: + description: Options to configure the Cryostat deployments metadata + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the resources during its + creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the resources during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + type: object + podMetadata: + description: Options to configure the Cryostat pods metadata + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the resources during its + creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the resources during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + type: object + type: object reportOptions: description: Options to configure Cryostat Automated Report Analysis. properties: diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index be742d9f..d0af1017 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -1074,6 +1074,45 @@ spec: type: object type: object type: object + operandMetadata: + description: Options to configure the Cryostat deployments and pods + metadata + properties: + deploymentMetadata: + description: Options to configure the Cryostat deployments metadata + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the resources during its + creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the resources during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + type: object + podMetadata: + description: Options to configure the Cryostat pods metadata + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the resources during its + creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the resources during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + type: object + type: object reportOptions: description: Options to configure Cryostat Automated Report Analysis. properties: diff --git a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml index da89290f..1f0d60c6 100644 --- a/config/crd/bases/operator.cryostat.io_clustercryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_clustercryostats.yaml @@ -1079,6 +1079,45 @@ spec: type: object type: object type: object + operandMetadata: + description: Options to configure the Cryostat deployments and pods + metadata + properties: + deploymentMetadata: + description: Options to configure the Cryostat deployments metadata + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the resources during its + creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the resources during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + type: object + podMetadata: + description: Options to configure the Cryostat pods metadata + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the resources during its + creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the resources during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + type: object + type: object reportOptions: description: Options to configure Cryostat Automated Report Analysis. properties: diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 086c2d5f..93fe3dcf 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -1075,6 +1075,45 @@ spec: type: object type: object type: object + operandMetadata: + description: Options to configure the Cryostat deployments and pods + metadata + properties: + deploymentMetadata: + description: Options to configure the Cryostat deployments metadata + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the resources during its + creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the resources during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + type: object + podMetadata: + description: Options to configure the Cryostat pods metadata + properties: + annotations: + additionalProperties: + type: string + description: Annotations to add to the resources during its + creation. + type: object + labels: + additionalProperties: + type: string + description: Labels to add to the resources during its creation. + The labels with keys "app" and "component" are reserved + for use by the operator. + type: object + type: object + type: object reportOptions: description: Options to configure Cryostat Automated Report Analysis. properties: diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 1fb3eb1c..6fd2a6d1 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -206,6 +206,15 @@ spec: label with key "app" is reserved for use by the operator. displayName: Labels path: networkOptions.grafanaConfig.labels + - description: Options to configure the Cryostat deployments and pods metadata + displayName: Operand metadata + path: operandMetadata + - description: Options to configure the Cryostat deployments metadata + displayName: Deployments metadata + path: operandMetadata.deploymentMetadata + - description: Options to configure the Cryostat pods metadata + displayName: Pods metadata + path: operandMetadata.podMetadata - description: Options to configure Cryostat Automated Report Analysis. displayName: Report Options path: reportOptions @@ -577,6 +586,15 @@ spec: label with key "app" is reserved for use by the operator. displayName: Labels path: networkOptions.grafanaConfig.labels + - description: Options to configure the Cryostat deployments and pods metadata + displayName: Operand metadata + path: operandMetadata + - description: Options to configure the Cryostat deployments metadata + displayName: Deployments metadata + path: operandMetadata.deploymentMetadata + - description: Options to configure the Cryostat pods metadata + displayName: Pods metadata + path: operandMetadata.podMetadata - description: Options to configure Cryostat Automated Report Analysis. displayName: Report Options path: reportOptions diff --git a/internal/controllers/common/common_utils.go b/internal/controllers/common/common_utils.go index 3c0cf3ea..b6c5310b 100644 --- a/internal/controllers/common/common_utils.go +++ b/internal/controllers/common/common_utils.go @@ -23,6 +23,7 @@ import ( "strings" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" logf "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -68,3 +69,22 @@ func ClusterUniqueName(kind string, name string, namespace string) string { suffix := fmt.Sprintf("%x", sha256.Sum256([]byte(nn.String()))) return strings.ToLower(kind) + "-" + suffix } + +func MergeLabelsAndAnnotations(dest *metav1.ObjectMeta, srcLabels, srcAnnotations map[string]string) { + // Check and create labels/annotations map if absent + if dest.Labels == nil { + dest.Labels = map[string]string{} + } + if dest.Annotations == nil { + dest.Annotations = map[string]string{} + } + + // Merge labels and annotations, preferring those in the source + for k, v := range srcLabels { + dest.Labels[k] = v + } + + for k, v := range srcAnnotations { + dest.Annotations[k] = v + } +} diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index bf540b5b..ad93b5a8 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -22,6 +22,7 @@ import ( "strings" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + common "github.com/cryostatio/cryostat-operator/internal/controllers/common" "github.com/cryostatio/cryostat-operator/internal/controllers/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/model" appsv1 "k8s.io/api/apps/v1" @@ -74,20 +75,63 @@ func NewDeploymentForCR(cr *model.CryostatInstance, specs *ServiceSpecs, imageTa tls *TLSConfig, fsGroup int64, openshift bool) *appsv1.Deployment { // Force one replica to avoid lock file and PVC contention replicas := int32(1) + + defaultDeploymentLabels := map[string]string{ + "app": cr.Name, + "kind": "cryostat", + "component": "cryostat", + "app.kubernetes.io/name": "cryostat", + } + defaultDeploymentAnnotations := map[string]string{ + "app.openshift.io/connects-to": "cryostat-operator-controller-manager", + } + defaultPodLabels := map[string]string{ + "app": cr.Name, + "kind": "cryostat", + "component": "cryostat", + } + userDefinedDeploymentLabels := make(map[string]string) + userDefinedDeploymentAnnotations := make(map[string]string) + userDefinedPodTemplateLabels := make(map[string]string) + userDefinedPodTemplateAnnotations := make(map[string]string) + if cr.Spec.OperandMetadata != nil { + if cr.Spec.OperandMetadata.DeploymentMetadata != nil { + for k, v := range cr.Spec.OperandMetadata.DeploymentMetadata.Labels { + userDefinedDeploymentLabels[k] = v + } + for k, v := range cr.Spec.OperandMetadata.DeploymentMetadata.Annotations { + userDefinedDeploymentAnnotations[k] = v + } + } + if cr.Spec.OperandMetadata.PodMetadata != nil { + for k, v := range cr.Spec.OperandMetadata.PodMetadata.Labels { + userDefinedPodTemplateLabels[k] = v + } + for k, v := range cr.Spec.OperandMetadata.PodMetadata.Annotations { + userDefinedPodTemplateAnnotations[k] = v + } + } + } + + // First set the user defined labels and annotation in the meta, so that the default ones can override them + deploymentMeta := metav1.ObjectMeta{ + Name: cr.Name, + Namespace: cr.InstallNamespace, + Labels: userDefinedDeploymentLabels, + Annotations: userDefinedDeploymentAnnotations, + } + common.MergeLabelsAndAnnotations(&deploymentMeta, defaultDeploymentLabels, defaultDeploymentAnnotations) + + podTemplateMeta := metav1.ObjectMeta{ + Name: cr.Name, + Namespace: cr.InstallNamespace, + Labels: userDefinedPodTemplateLabels, + Annotations: userDefinedPodTemplateAnnotations, + } + common.MergeLabelsAndAnnotations(&podTemplateMeta, defaultPodLabels, nil) + return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: cr.Name, - Namespace: cr.InstallNamespace, - Labels: map[string]string{ - "app": cr.Name, - "kind": "cryostat", - "component": "cryostat", - "app.kubernetes.io/name": "cryostat", - }, - Annotations: map[string]string{ - "app.openshift.io/connects-to": "cryostat-operator-controller-manager", - }, - }, + ObjectMeta: deploymentMeta, Spec: appsv1.DeploymentSpec{ // Selector is immutable, avoid modifying if possible Selector: &metav1.LabelSelector{ @@ -98,16 +142,8 @@ func NewDeploymentForCR(cr *model.CryostatInstance, specs *ServiceSpecs, imageTa }, }, Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: cr.Name, - Namespace: cr.InstallNamespace, - Labels: map[string]string{ - "app": cr.Name, - "kind": "cryostat", - "component": "cryostat", - }, - }, - Spec: *NewPodForCR(cr, specs, imageTags, tls, fsGroup, openshift), + ObjectMeta: podTemplateMeta, + Spec: *NewPodForCR(cr, specs, imageTags, tls, fsGroup, openshift), }, Replicas: &replicas, Strategy: appsv1.DeploymentStrategy{ @@ -123,20 +159,63 @@ func NewDeploymentForReports(cr *model.CryostatInstance, imageTags *ImageTags, t if cr.Spec.ReportOptions != nil { replicas = cr.Spec.ReportOptions.Replicas } + + defaultDeploymentLabels := map[string]string{ + "app": cr.Name, + "kind": "cryostat", + "component": "reports", + "app.kubernetes.io/name": "cryostat-reports", + } + defaultDeploymentAnnotations := map[string]string{ + "app.openshift.io/connects-to": cr.Name, + } + defaultPodLabels := map[string]string{ + "app": cr.Name, + "kind": "cryostat", + "component": "reports", + } + userDefinedDeploymentLabels := make(map[string]string) + userDefinedDeploymentAnnotations := make(map[string]string) + userDefinedPodTemplateLabels := make(map[string]string) + userDefinedPodTemplateAnnotations := make(map[string]string) + if cr.Spec.OperandMetadata != nil { + if cr.Spec.OperandMetadata.DeploymentMetadata != nil { + for k, v := range cr.Spec.OperandMetadata.DeploymentMetadata.Labels { + userDefinedDeploymentLabels[k] = v + } + for k, v := range cr.Spec.OperandMetadata.DeploymentMetadata.Annotations { + userDefinedDeploymentAnnotations[k] = v + } + } + if cr.Spec.OperandMetadata.PodMetadata != nil { + for k, v := range cr.Spec.OperandMetadata.PodMetadata.Labels { + userDefinedPodTemplateLabels[k] = v + } + for k, v := range cr.Spec.OperandMetadata.PodMetadata.Annotations { + userDefinedPodTemplateAnnotations[k] = v + } + } + } + + // First set the user defined labels and annotation in the meta, so that the default ones can override them + deploymentMeta := metav1.ObjectMeta{ + Name: cr.Name + "-reports", + Namespace: cr.InstallNamespace, + Labels: userDefinedDeploymentLabels, + Annotations: userDefinedDeploymentAnnotations, + } + common.MergeLabelsAndAnnotations(&deploymentMeta, defaultDeploymentLabels, defaultDeploymentAnnotations) + + podTemplateMeta := metav1.ObjectMeta{ + Name: cr.Name + "-reports", + Namespace: cr.InstallNamespace, + Labels: userDefinedPodTemplateLabels, + Annotations: userDefinedPodTemplateAnnotations, + } + common.MergeLabelsAndAnnotations(&podTemplateMeta, defaultPodLabels, nil) + return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: cr.Name + "-reports", - Namespace: cr.InstallNamespace, - Labels: map[string]string{ - "app": cr.Name, - "kind": "cryostat", - "component": "reports", - "app.kubernetes.io/name": "cryostat-reports", - }, - Annotations: map[string]string{ - "app.openshift.io/connects-to": cr.Name, - }, - }, + ObjectMeta: deploymentMeta, Spec: appsv1.DeploymentSpec{ // Selector is immutable, avoid modifying if possible Selector: &metav1.LabelSelector{ @@ -147,16 +226,8 @@ func NewDeploymentForReports(cr *model.CryostatInstance, imageTags *ImageTags, t }, }, Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: cr.Name + "-reports", - Namespace: cr.InstallNamespace, - Labels: map[string]string{ - "app": cr.Name, - "kind": "cryostat", - "component": "reports", - }, - }, - Spec: *NewPodForReports(cr, imageTags, tls, openshift), + ObjectMeta: podTemplateMeta, + Spec: *NewPodForReports(cr, imageTags, tls, openshift), }, Replicas: &replicas, }, diff --git a/internal/controllers/ingresses.go b/internal/controllers/ingresses.go index 84c7fe67..cadb9156 100644 --- a/internal/controllers/ingresses.go +++ b/internal/controllers/ingresses.go @@ -20,6 +20,7 @@ import ( "net/url" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + common "github.com/cryostatio/cryostat-operator/internal/controllers/common" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" "github.com/cryostatio/cryostat-operator/internal/controllers/model" netv1 "k8s.io/api/networking/v1" @@ -101,7 +102,7 @@ func (r *Reconciler) createOrUpdateIngress(ctx context.Context, ingress *netv1.I config *operatorv1beta1.NetworkConfiguration) (*netv1.Ingress, error) { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, ingress, func() error { // Set labels and annotations from CR - mergeLabelsAndAnnotations(&ingress.ObjectMeta, config.Labels, config.Annotations) + common.MergeLabelsAndAnnotations(&ingress.ObjectMeta, config.Labels, config.Annotations) // Set the Cryostat CR as controller if err := controllerutil.SetControllerReference(owner, ingress, r.Scheme); err != nil { diff --git a/internal/controllers/pvc.go b/internal/controllers/pvc.go index e9758964..49bdc37b 100644 --- a/internal/controllers/pvc.go +++ b/internal/controllers/pvc.go @@ -19,6 +19,7 @@ import ( "fmt" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + common "github.com/cryostatio/cryostat-operator/internal/controllers/common" "github.com/cryostatio/cryostat-operator/internal/controllers/model" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -64,7 +65,7 @@ func (r *Reconciler) createOrUpdatePVC(ctx context.Context, pvc *corev1.Persiste owner metav1.Object, config *operatorv1beta1.PersistentVolumeClaimConfig) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, pvc, func() error { // Merge labels and annotations to prevent overriding any set by Kubernetes - mergeLabelsAndAnnotations(&pvc.ObjectMeta, config.Labels, config.Annotations) + common.MergeLabelsAndAnnotations(&pvc.ObjectMeta, config.Labels, config.Annotations) // Set the Cryostat CR as controller if err := controllerutil.SetControllerReference(owner, pvc, r.Scheme); err != nil { diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 837db709..f0fdddc2 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -24,7 +24,7 @@ import ( certv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" - "github.com/cryostatio/cryostat-operator/internal/controllers/common" + common "github.com/cryostatio/cryostat-operator/internal/controllers/common" resources "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" "github.com/cryostatio/cryostat-operator/internal/controllers/model" "github.com/go-logr/logr" @@ -440,9 +440,8 @@ var errSelectorModified error = errors.New("deployment selector has been modifie func (r *Reconciler) createOrUpdateDeployment(ctx context.Context, deploy *appsv1.Deployment, owner metav1.Object) error { deployCopy := deploy.DeepCopy() op, err := controllerutil.CreateOrUpdate(ctx, r.Client, deploy, func() error { - // TODO consider managing labels and annotations using CRD // Merge any required labels and annotations - mergeLabelsAndAnnotations(&deploy.ObjectMeta, deployCopy.Labels, deployCopy.Annotations) + common.MergeLabelsAndAnnotations(&deploy.ObjectMeta, deployCopy.Labels, deployCopy.Annotations) // Set the Cryostat CR as controller if err := controllerutil.SetControllerReference(owner, deploy, r.Scheme); err != nil { return err @@ -461,7 +460,7 @@ func (r *Reconciler) createOrUpdateDeployment(ctx context.Context, deploy *appsv // Update pod template spec to propagate any changes from Cryostat CR deploy.Spec.Template.Spec = deployCopy.Spec.Template.Spec // Update pod template metadata - mergeLabelsAndAnnotations(&deploy.Spec.Template.ObjectMeta, deployCopy.Spec.Template.Labels, + common.MergeLabelsAndAnnotations(&deploy.Spec.Template.ObjectMeta, deployCopy.Spec.Template.Labels, deployCopy.Spec.Template.Annotations) return nil }) @@ -543,21 +542,3 @@ func findDeployCondition(conditions []appsv1.DeploymentCondition, condType appsv } return nil } - -func mergeLabelsAndAnnotations(dest *metav1.ObjectMeta, srcLabels, srcAnnotations map[string]string) { - // Check and create labels/annotations map if absent - if dest.Labels == nil { - dest.Labels = map[string]string{} - } - if dest.Annotations == nil { - dest.Annotations = map[string]string{} - } - - // Merge labels and annotations, preferring those in the source - for k, v := range srcLabels { - dest.Labels[k] = v - } - for k, v := range srcAnnotations { - dest.Annotations[k] = v - } -} diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 064acab8..2358efe6 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -2157,6 +2157,19 @@ func (c *controllerTest) commonTests() { Expect(binding.RoleRef).To(Equal(expected.RoleRef)) }) }) + Context("with additionnal label and annotation", func() { + BeforeEach(func() { + t.ReportReplicas = 1 + t.objs = append(t.objs, t.NewCryostatWithAdditionalMetadata().Object) + }) + JustBeforeEach(func() { + t.reconcileCryostatFully() + }) + It("should have added the extra label and annotation to deployments and pods", func() { + t.expectMainDeploymentHasExtraMetadata() + t.expectReportsDeploymentHasExtraMetadata() + }) + }) }) } @@ -2604,6 +2617,30 @@ func (t *cryostatTestInput) expectMainDeployment() { t.checkMainPodTemplate(deployment, cr) } +func (t *cryostatTestInput) expectMainDeploymentHasExtraMetadata() { + deployment := &appsv1.Deployment{} + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) + Expect(err).ToNot(HaveOccurred()) + + cr := t.getCryostatInstance() + + Expect(deployment.Annotations).To(Equal(map[string]string{ + "app.openshift.io/connects-to": "cryostat-operator-controller-manager", + "myDeploymentExtraAnnotation": "myDeploymentAnnotation", + "mySecondDeploymentExtraAnnotation": "mySecondDeploymentAnnotation", + })) + Expect(deployment.Labels).To(Equal(map[string]string{ + "app": t.Name, + "kind": "cryostat", + "component": "cryostat", + "app.kubernetes.io/name": "cryostat", + "myDeploymentExtraLabel": "myDeploymentLabel", + "mySecondDeploymentExtraLabel": "mySecondDeploymentLabel", + })) + // compare Pod template + t.expectMainPodTemplateHasExtraMetadata(deployment, cr) +} + func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, cr *model.CryostatInstance) { template := deployment.Spec.Template Expect(template.Name).To(Equal(t.Name)) @@ -2666,6 +2703,21 @@ func (t *cryostatTestInput) checkMainPodTemplate(deployment *appsv1.Deployment, } } +func (t *cryostatTestInput) expectMainPodTemplateHasExtraMetadata(deployment *appsv1.Deployment, cr *model.CryostatInstance) { + template := deployment.Spec.Template + Expect(template.Labels).To(Equal(map[string]string{ + "app": t.Name, + "kind": "cryostat", + "component": "cryostat", + "myPodExtraLabel": "myPodLabel", + "myPodSecondExtraLabel": "myPodSecondLabel", + })) + Expect(template.Annotations).To(Equal(map[string]string{ + "myPodExtraAnnotation": "myPodAnnotation", + "mySecondPodExtraAnnotation": "mySecondPodAnnotation", + })) +} + func (t *cryostatTestInput) checkReportsDeployment() { deployment := &appsv1.Deployment{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-reports", Namespace: t.Namespace}, deployment) @@ -2720,6 +2772,37 @@ func (t *cryostatTestInput) checkReportsDeployment() { } } +func (t *cryostatTestInput) expectReportsDeploymentHasExtraMetadata() { + reportDeployment := &appsv1.Deployment{} + err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name + "-reports", Namespace: t.Namespace}, reportDeployment) + Expect(err).ToNot(HaveOccurred()) + Expect(reportDeployment.Annotations).To(Equal(map[string]string{ + "app.openshift.io/connects-to": t.Name, + "myDeploymentExtraAnnotation": "myDeploymentAnnotation", + "mySecondDeploymentExtraAnnotation": "mySecondDeploymentAnnotation", + })) + Expect(reportDeployment.Labels).To(Equal(map[string]string{ + "app": t.Name, + "kind": "cryostat", + "component": "reports", + "app.kubernetes.io/name": "cryostat-reports", + "myDeploymentExtraLabel": "myDeploymentLabel", + "mySecondDeploymentExtraLabel": "mySecondDeploymentLabel", + })) + template := reportDeployment.Spec.Template + Expect(template.Labels).To(Equal(map[string]string{ + "app": t.Name, + "kind": "cryostat", + "component": "reports", + "myPodExtraLabel": "myPodLabel", + "myPodSecondExtraLabel": "myPodSecondLabel", + })) + Expect(template.Annotations).To(Equal(map[string]string{ + "myPodExtraAnnotation": "myPodAnnotation", + "mySecondPodExtraAnnotation": "mySecondPodAnnotation", + })) +} + func (t *cryostatTestInput) checkDeploymentHasTemplates() { deployment := &appsv1.Deployment{} err := t.Client.Get(context.Background(), types.NamespacedName{Name: t.Name, Namespace: t.Namespace}, deployment) diff --git a/internal/controllers/routes.go b/internal/controllers/routes.go index a1930552..97564639 100644 --- a/internal/controllers/routes.go +++ b/internal/controllers/routes.go @@ -21,6 +21,7 @@ import ( "net/url" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + common "github.com/cryostatio/cryostat-operator/internal/controllers/common" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" "github.com/cryostatio/cryostat-operator/internal/controllers/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/model" @@ -117,7 +118,7 @@ func (r *Reconciler) createOrUpdateRoute(ctx context.Context, route *routev1.Rou op, err := controllerutil.CreateOrUpdate(ctx, r.Client, route, func() error { // Set labels and annotations from CR - mergeLabelsAndAnnotations(&route.ObjectMeta, config.Labels, config.Annotations) + common.MergeLabelsAndAnnotations(&route.ObjectMeta, config.Labels, config.Annotations) // Set the Cryostat CR as controller if err := controllerutil.SetControllerReference(owner, route, r.Scheme); err != nil { diff --git a/internal/controllers/services.go b/internal/controllers/services.go index 22278848..2332ee0c 100644 --- a/internal/controllers/services.go +++ b/internal/controllers/services.go @@ -21,6 +21,7 @@ import ( "strconv" operatorv1beta1 "github.com/cryostatio/cryostat-operator/api/v1beta1" + common "github.com/cryostatio/cryostat-operator/internal/controllers/common" "github.com/cryostatio/cryostat-operator/internal/controllers/common/resource_definitions" "github.com/cryostatio/cryostat-operator/internal/controllers/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/model" @@ -246,7 +247,7 @@ func (r *Reconciler) createOrUpdateService(ctx context.Context, svc *corev1.Serv config *operatorv1beta1.ServiceConfig, delegate controllerutil.MutateFn) error { op, err := controllerutil.CreateOrUpdate(ctx, r.Client, svc, func() error { // Update labels and annotations - mergeLabelsAndAnnotations(&svc.ObjectMeta, config.Labels, config.Annotations) + common.MergeLabelsAndAnnotations(&svc.ObjectMeta, config.Labels, config.Annotations) // Set the Cryostat CR as controller if err := controllerutil.SetControllerReference(owner, svc, r.Scheme); err != nil { diff --git a/internal/test/resources.go b/internal/test/resources.go index fae7503e..ad7e454c 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -654,6 +654,44 @@ func (r *TestResources) NewCryostatWithDatabaseSecretProvided() *model.CryostatI return cr } +func (r *TestResources) NewCryostatWithAdditionalMetadata() *model.CryostatInstance { + cr := r.NewCryostat() + cr.Spec.OperandMetadata = &operatorv1beta1.OperandMetadata{ + DeploymentMetadata: &operatorv1beta1.ResourceMetadata{ + Labels: map[string]string{ + "myDeploymentExtraLabel": "myDeploymentLabel", + "mySecondDeploymentExtraLabel": "mySecondDeploymentLabel", + // below, labels that should be discarded as overriden by the default + "app": "myApp", + "component": "myComponent", + "kind": "myKind", + "app.kubernetes.io/name": "myName", + }, + Annotations: map[string]string{ + "myDeploymentExtraAnnotation": "myDeploymentAnnotation", + "mySecondDeploymentExtraAnnotation": "mySecondDeploymentAnnotation", + // below, annotation that should be discarded as overriden by the default + "app.openshift.io/connects-to": "connectToMe", + }, + }, + PodMetadata: &operatorv1beta1.ResourceMetadata{ + Labels: map[string]string{ + "myPodExtraLabel": "myPodLabel", + "myPodSecondExtraLabel": "myPodSecondLabel", + // below, labels that should be discarded as overriden by the default + "app": "myApp", + "component": "myComponent", + "kind": "myKind", + }, + Annotations: map[string]string{ + "myPodExtraAnnotation": "myPodAnnotation", + "mySecondPodExtraAnnotation": "mySecondPodAnnotation", + }, + }, + } + return cr +} + func (r *TestResources) NewCryostatService() *corev1.Service { c := true return &corev1.Service{