feat: configure k8s recommended labels on subresources

Signed-off-by: Gabriele Fedi <gabriele.fedi@enterprisedb.com>
This commit is contained in:
Gabriele Fedi 2026-04-20 17:12:43 +02:00
parent 742a0c0810
commit e3835f1c0c
4 changed files with 114 additions and 42 deletions

View File

@ -31,7 +31,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
)
@ -62,9 +61,7 @@ func EnsureRole(
return err
}
return patchRole(ctx, c, roleKey, newRole.Rules, map[string]string{
metadata.ClusterLabelName: cluster.Name,
})
return patchRole(ctx, c, roleKey, newRole.Rules, specs.GetRequiredLabels(cluster))
}
// EnsureRoleRules updates the rules of an existing Role to match
@ -90,6 +87,48 @@ func EnsureRoleRules(
return err
}
// EnsureRoleBinding ensures the RoleBinding for the given Cluster matches
// the desired state.
//
// This function is called from the Pre hook (gRPC). It creates the RoleBinding
// if it does not exist, otherwise it patches RoleRef, Subjects, and labels to match
// the desired state.
func EnsureRoleBinding(ctx context.Context, c client.Client, cluster *cnpgv1.Cluster) error {
contextLogger := log.FromContext(ctx)
desiredRoleBinding := specs.BuildRoleBinding(cluster)
if err := specs.SetControllerReference(cluster, desiredRoleBinding); err != nil {
return err
}
roleBinding := &rbacv1.RoleBinding{}
if err := c.Get(ctx, client.ObjectKey{
Namespace: cluster.Namespace,
Name: specs.GetRBACName(cluster.Name),
}, roleBinding); err != nil {
if apierrs.IsNotFound(err) {
contextLogger.Info("Creating RoleBinding", "name", desiredRoleBinding.Name,
"namespace", desiredRoleBinding.Namespace)
return c.Create(ctx, desiredRoleBinding)
}
}
if !roleBindingNeedsUpdate(roleBinding, desiredRoleBinding) {
return nil
}
contextLogger.Info("Patching role binding",
"name", roleBinding.Name, "namespace", roleBinding.Namespace)
oldRoleBinding := roleBinding.DeepCopy()
roleBinding.Labels = desiredRoleBinding.Labels
roleBinding.RoleRef = desiredRoleBinding.RoleRef
roleBinding.Subjects = desiredRoleBinding.Subjects
return c.Patch(ctx, roleBinding, client.MergeFrom(oldRoleBinding))
}
// ensureRoleExists creates the Role if it does not exist. Returns
// nil on success and nil on AlreadyExists (another writer created
// it concurrently). The caller always follows up with patchRole.
@ -179,3 +218,25 @@ func labelsNeedUpdate(existing, desired map[string]string) bool {
}
return false
}
// roleBindingNeedsUpdate returns true if the existing RoleBinding's
// RoleRef or Subjects differ from the desired, or if labels need update.
func roleBindingNeedsUpdate(existing, desired *rbacv1.RoleBinding) bool {
if existing == nil || desired == nil {
return existing != desired
}
if !equality.Semantic.DeepEqual(existing.RoleRef, desired.RoleRef) {
return true
}
if !equality.Semantic.DeepEqual(existing.Subjects, desired.Subjects) {
return true
}
if labelsNeedUpdate(existing.Labels, desired.Labels) {
return true
}
return false
}

View File

@ -27,14 +27,12 @@ import (
"github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/object"
"github.com/cloudnative-pg/cnpg-i/pkg/reconciler"
"github.com/cloudnative-pg/machinery/pkg/log"
rbacv1 "k8s.io/api/rbac/v1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/rbac"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
)
// ReconcilerImplementation implements the Reconciler capability
@ -117,7 +115,7 @@ func (r ReconcilerImplementation) Pre(
return nil, err
}
if err := r.ensureRoleBinding(ctx, &cluster); err != nil {
if err := rbac.EnsureRoleBinding(ctx, r.Client, &cluster); err != nil {
return nil, err
}
@ -136,34 +134,3 @@ func (r ReconcilerImplementation) Post(
Behavior: reconciler.ReconcilerHooksResult_BEHAVIOR_CONTINUE,
}, nil
}
func (r ReconcilerImplementation) ensureRoleBinding(
ctx context.Context,
cluster *cnpgv1.Cluster,
) error {
var roleBinding rbacv1.RoleBinding
if err := r.Client.Get(ctx, client.ObjectKey{
Namespace: cluster.Namespace,
Name: specs.GetRBACName(cluster.Name),
}, &roleBinding); err != nil {
if apierrs.IsNotFound(err) {
return r.createRoleBinding(ctx, cluster)
}
return err
}
// TODO: this assumes role bindings never change.
// Is that true? Should we relax this assumption?
return nil
}
func (r ReconcilerImplementation) createRoleBinding(
ctx context.Context,
cluster *cnpgv1.Cluster,
) error {
roleBinding := specs.BuildRoleBinding(cluster)
if err := specs.SetControllerReference(cluster, roleBinding); err != nil {
return err
}
return r.Client.Create(ctx, roleBinding)
}

View File

@ -0,0 +1,46 @@
/*
Copyright © contributors to CloudNativePG, established as
CloudNativePG a Series of LF Projects, LLC.
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.
SPDX-License-Identifier: Apache-2.0
*/
package specs
import (
"fmt"
cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
"github.com/cloudnative-pg/cloudnative-pg/pkg/utils"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
)
// GetRequiredLabels returns the labels that must be present on all plugin-managed objects for a given Cluster.
func GetRequiredLabels(cluster *cnpgv1.Cluster) map[string]string {
requiredLabels := map[string]string{
metadata.ClusterLabelName: cluster.Name,
// Kubernetes recommended labels
utils.KubernetesAppLabelName: utils.AppName,
utils.KubernetesAppInstanceLabelName: cluster.Name,
utils.KubernetesAppManagedByLabelName: "plugin-barman-cloud",
utils.KubernetesAppComponentLabelName: utils.DatabaseComponentName,
}
if version, err := cluster.GetPostgresqlMajorVersion(); err == nil && version != 0 {
requiredLabels[utils.KubernetesAppVersionLabelName] = fmt.Sprint(version)
}
return requiredLabels
}

View File

@ -29,7 +29,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
)
// BuildRole builds the Role object for this cluster
@ -41,9 +40,7 @@ func BuildRole(
ObjectMeta: metav1.ObjectMeta{
Namespace: cluster.Namespace,
Name: GetRBACName(cluster.Name),
Labels: map[string]string{
metadata.ClusterLabelName: cluster.Name,
},
Labels: GetRequiredLabels(cluster),
},
Rules: BuildRoleRules(barmanObjects),
}
@ -131,6 +128,7 @@ func BuildRoleBinding(
ObjectMeta: metav1.ObjectMeta{
Namespace: cluster.Namespace,
Name: GetRBACName(cluster.Name),
Labels: GetRequiredLabels(cluster),
},
Subjects: []rbacv1.Subject{
{