@@ -29,130 +29,149 @@ import (
2929 rbacv1 "k8s.io/api/rbac/v1"
3030 "k8s.io/apimachinery/pkg/api/equality"
3131 apierrs "k8s.io/apimachinery/pkg/api/errors"
32+ "k8s.io/client-go/util/retry"
3233 "sigs.k8s.io/controller-runtime/pkg/client"
3334 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3435
3536 barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
37+ "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
3638 "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/specs"
3739)
3840
3941// EnsureRole ensures the RBAC Role for the given Cluster matches
4042// the desired state derived from the given ObjectStores. On creation,
4143// the Cluster is set as the owner of the Role for garbage collection.
4244//
43- // This function is called from both the Pre hook (gRPC) and the
44- // ObjectStore controller. To handle concurrent modifications
45- // gracefully, AlreadyExists on Create and Conflict on Patch are
46- // retried once rather than returned as errors.
45+ // This function is called from the Pre hook (gRPC). It creates the
46+ // Role if it does not exist, then patches rules and labels to match
47+ // the desired state.
4748func EnsureRole (
4849 ctx context.Context ,
4950 c client.Client ,
5051 cluster * cnpgv1.Cluster ,
5152 barmanObjects []barmancloudv1.ObjectStore ,
5253) error {
5354 newRole := specs .BuildRole (cluster , barmanObjects )
55+ roleKey := client .ObjectKeyFromObject (newRole )
5456
55- roleKey := client.ObjectKey {
56- Namespace : newRole .Namespace ,
57- Name : newRole .Name ,
57+ if err := ensureRoleExists (ctx , c , cluster , newRole ); err != nil {
58+ return err
5859 }
5960
60- var role rbacv1.Role
61- err := c .Get (ctx , roleKey , & role )
62-
63- switch {
64- case apierrs .IsNotFound (err ):
65- role , err := createRole (ctx , c , cluster , newRole )
66- if err != nil {
67- return err
68- }
69- if role == nil {
70- // Created successfully, nothing else to do.
71- return nil
72- }
73- // AlreadyExists: fall through to patch with the re-read role.
74- return patchRoleRules (ctx , c , newRole .Rules , role )
75-
76- case err != nil :
77- return err
61+ return patchRole (ctx , c , roleKey , newRole .Rules , map [string ]string {
62+ metadata .ClusterLabelName : cluster .Name ,
63+ })
64+ }
7865
79- default :
80- return patchRoleRules (ctx , c , newRole .Rules , & role )
66+ // EnsureRoleRules updates the rules of an existing Role to match
67+ // the desired state derived from the given ObjectStores. Unlike
68+ // EnsureRole, this function does not create Roles or set owner
69+ // references — it only patches rules on Roles that already exist.
70+ // It is intended for the ObjectStore controller path where no
71+ // Cluster object is available. Returns nil if the Role does not
72+ // exist (the Pre hook has not created it yet).
73+ func EnsureRoleRules (
74+ ctx context.Context ,
75+ c client.Client ,
76+ roleKey client.ObjectKey ,
77+ barmanObjects []barmancloudv1.ObjectStore ,
78+ ) error {
79+ err := patchRole (ctx , c , roleKey , specs .BuildRoleRules (barmanObjects ), nil )
80+ if apierrs .IsNotFound (err ) {
81+ log .FromContext (ctx ).Debug ("Role not found, skipping rule update" ,
82+ "name" , roleKey .Name , "namespace" , roleKey .Namespace )
83+ return nil
8184 }
85+
86+ return err
8287}
8388
84- // createRole attempts to create the Role. If another writer created
85- // it concurrently (AlreadyExists), it re-reads and returns the
86- // existing Role for the caller to patch. On success it returns nil .
87- func createRole (
89+ // ensureRoleExists creates the Role if it does not exist. Returns
90+ // nil on success and nil on AlreadyExists (another writer created
91+ // it concurrently). The caller always follows up with patchRole .
92+ func ensureRoleExists (
8893 ctx context.Context ,
8994 c client.Client ,
9095 cluster * cnpgv1.Cluster ,
9196 newRole * rbacv1.Role ,
92- ) ( * rbacv1. Role , error ) {
97+ ) error {
9398 contextLogger := log .FromContext (ctx )
9499
100+ var existing rbacv1.Role
101+ err := c .Get (ctx , client .ObjectKeyFromObject (newRole ), & existing )
102+ if err == nil {
103+ return nil
104+ }
105+ if ! apierrs .IsNotFound (err ) {
106+ return err
107+ }
108+
95109 if err := controllerutil .SetControllerReference (cluster , newRole , c .Scheme ()); err != nil {
96- return nil , err
110+ return err
97111 }
98112
99113 contextLogger .Info ("Creating role" ,
100114 "name" , newRole .Name , "namespace" , newRole .Namespace )
101115
102116 createErr := c .Create (ctx , newRole )
103- if createErr == nil {
104- return nil , nil
105- }
106- if ! apierrs .IsAlreadyExists (createErr ) {
107- return nil , createErr
108- }
109-
110- contextLogger .Info ("Role was created concurrently, checking rules" )
111-
112- var role rbacv1.Role
113- if err := c .Get (ctx , client .ObjectKeyFromObject (newRole ), & role ); err != nil {
114- return nil , err
117+ if createErr == nil || apierrs .IsAlreadyExists (createErr ) {
118+ return nil
115119 }
116120
117- return & role , nil
121+ return createErr
118122}
119123
120- // patchRoleRules patches the Role's rules if they differ from the
121- // desired state. On Conflict (concurrent modification), it retries
122- // once with a fresh read.
123- func patchRoleRules (
124+ // patchRole patches the Role's rules and optionally its labels to
125+ // match the desired state. When desiredLabels is nil, labels are
126+ // not modified. Uses retry.RetryOnConflict for concurrent
127+ // modification handling.
128+ func patchRole (
124129 ctx context.Context ,
125130 c client.Client ,
131+ roleKey client.ObjectKey ,
126132 desiredRules []rbacv1.PolicyRule ,
127- role * rbacv1. Role ,
133+ desiredLabels map [ string ] string ,
128134) error {
129- if equality .Semantic .DeepEqual (desiredRules , role .Rules ) {
130- return nil
131- }
135+ return retry .RetryOnConflict (retry .DefaultBackoff , func () error {
136+ var role rbacv1.Role
137+ if err := c .Get (ctx , roleKey , & role ); err != nil {
138+ return err
139+ }
132140
133- contextLogger := log .FromContext (ctx )
134- contextLogger .Info ("Patching role" ,
135- "name" , role .Name , "namespace" , role .Namespace , "rules" , desiredRules )
141+ rulesMatch := equality .Semantic .DeepEqual (desiredRules , role .Rules )
142+ labelsMatch := desiredLabels == nil || ! labelsNeedUpdate (role .Labels , desiredLabels )
143+
144+ if rulesMatch && labelsMatch {
145+ return nil
146+ }
136147
137- oldRole := role .DeepCopy ()
138- role .Rules = desiredRules
148+ contextLogger := log .FromContext (ctx )
149+ contextLogger .Info ("Patching role" ,
150+ "name" , role .Name , "namespace" , role .Namespace )
139151
140- patchErr := c .Patch (ctx , role , client .MergeFrom (oldRole ))
141- if patchErr == nil || ! apierrs .IsConflict (patchErr ) {
142- return patchErr
143- }
152+ oldRole := role .DeepCopy ()
153+ role .Rules = desiredRules
144154
145- // Conflict: re-read and retry once.
146- contextLogger . Info ( "Role was modified concurrently, retrying patch" )
147- if err := c . Get ( ctx , client . ObjectKeyFromObject ( role ), role ); err != nil {
148- return err
149- }
150- if equality . Semantic . DeepEqual ( desiredRules , role .Rules ) {
151- return nil
152- }
155+ if desiredLabels != nil {
156+ if role . Labels == nil {
157+ role . Labels = make ( map [ string ] string , len ( desiredLabels ))
158+ }
159+ for k , v := range desiredLabels {
160+ role .Labels [ k ] = v
161+ }
162+ }
153163
154- oldRole = role .DeepCopy ()
155- role .Rules = desiredRules
164+ return c .Patch (ctx , & role , client .MergeFrom (oldRole ))
165+ })
166+ }
156167
157- return c .Patch (ctx , role , client .MergeFrom (oldRole ))
168+ // labelsNeedUpdate returns true if any key in desired is missing
169+ // or has a different value in existing.
170+ func labelsNeedUpdate (existing , desired map [string ]string ) bool {
171+ for k , v := range desired {
172+ if existing [k ] != v {
173+ return true
174+ }
175+ }
176+ return false
158177}
0 commit comments