@@ -141,17 +141,8 @@ func (r *BucketAccessReconciler) reconcile(
141141 }
142142
143143 if ! access .GetDeletionTimestamp ().IsZero () {
144- logger .V (1 ).Info ("beginning BucketAccess deletion cleanup" )
145-
146- // TODO: deletion logic
147-
148- ctrlutil .RemoveFinalizer (access , cosiapi .ProtectionFinalizer )
149- if err := r .Update (ctx , access ); err != nil {
150- logger .Error (err , "failed to remove finalizer" )
151- return fmt .Errorf ("failed to remove finalizer: %w" , err )
152- }
153-
154- return cosierr .NonRetryableError (fmt .Errorf ("deletion is not yet implemented" )) // TODO
144+ logger .V (1 ).Info ("beginning BucketAccess deletion" )
145+ return r .reconcileDelete (ctx , logger , access )
155146 }
156147
157148 initialized , err := bucketaccess .SidecarRequirementsPresent (& access .Status )
@@ -191,7 +182,7 @@ func (r *BucketAccessReconciler) reconcile(
191182 return err
192183 }
193184
194- internalCfg , err := newInternalAccessConfig (access , bucketsByName , secretsByName )
185+ internalCfg , err := newInternalGrantAccessConfig (access , bucketsByName , secretsByName )
195186 if err != nil {
196187 logger .Error (err , "failed to build internal representation of access configuration" )
197188 return fmt .Errorf ("failed to build internal representation of access configuration: %w" , err )
@@ -252,34 +243,79 @@ func (r *BucketAccessReconciler) reconcile(
252243 return nil
253244}
254245
246+ func (r * BucketAccessReconciler ) reconcileDelete (
247+ ctx context.Context , logger logr.Logger , access * cosiapi.BucketAccess ,
248+ ) error {
249+ if err := r .deleteOwnedAccessSecrets (ctx , logger , access ); err != nil {
250+ logger .Error (err , "failed to ensure deletion of access Secrets" )
251+ return err
252+ }
253+
254+ resp , err := r .DriverInfo .provisionerClient .DriverRevokeBucketAccess (ctx ,
255+ & cosiproto.DriverRevokeBucketAccessRequest {
256+ AccountName : access .Status .AccountID ,
257+ Protocol : & cosiproto.ObjectProtocol {Type : internalCfg .Protocol },
258+ AuthenticationType : & cosiproto.AuthenticationType {Type : internalCfg .AuthenticationType },
259+ ServiceAccountName : internalCfg .ServiceAccountName ,
260+ Parameters : internalCfg .Parameters ,
261+ Buckets : internalCfg .RpcAccessedBucketsList (),
262+ },
263+ )
264+ if err != nil {
265+ logger .Error (err , "DriverRevokeBucketAccess error" )
266+ if rpcErrorIsRetryable (status .Code (err )) {
267+ return err
268+ }
269+ // Do not proceed with k8s resource cleanup after a non-retryable error. Proceeding would
270+ // risk leaving backend resources orphaned without any clear indication to administrators
271+ // that they need to do manual cleanup if desired. If this is a sidecar or driver error, an
272+ // update could resolve the issue to allow a future deletion to succeed.
273+ return cosierr .NonRetryableError (err )
274+ }
275+
276+ // Do not remove finalizer. Controller will do that after it cleans up.
277+
278+ return nil
279+ }
280+
255281// Internal representation of access configuration.
256282type internalAccessConfig struct {
257- AccountName string
258283 Protocol cosiproto.ObjectProtocol_Type
259284 AuthenticationType cosiproto.AuthenticationType_Type
260285 ServiceAccountName string
261286 Parameters map [string ]string
262287
263- AccessConfigsByBucketId map [string ]bucketAccessConfig
264-
265288 BucketsByName map [string ]* cosiapi.Bucket
289+ }
290+
291+ // Internal representation of access configuration for granting access.
292+ type internalGrantAccessConfig struct {
293+ internalAccessConfig
294+
295+ AccountName string
296+ AccessConfigsByBucketId map [string ]bucketGrantAccessConfig
297+
266298 SecretsByName map [string ]* corev1.Secret
267299}
268300
269- // Internal access configuration for a specific bucket.
270- type bucketAccessConfig struct {
301+ // Internal grant- access configuration for a specific bucket.
302+ type bucketGrantAccessConfig struct {
271303 AccessMode cosiproto.AccessMode_Mode
272304 AccessSecretName string
273305}
274306
307+ // Internal representation of access configuration for revoking access.
308+ type internalRevokeAccessConfig struct {
309+ internalAccessConfig
310+
311+ AccessID string
312+ }
313+
275314// Parse the access, and generate a new internal access config struct for follow-up.
276315func newInternalAccessConfig (
277316 access * cosiapi.BucketAccess ,
278317 bucketsByName map [string ]* cosiapi.Bucket ,
279- secretsByName map [string ]* corev1.Secret ,
280318) (* internalAccessConfig , error ) {
281- acctName := "ba-" + string (access .UID ) // DO NOT CHANGE
282-
283319 proto , err := protocol.ObjectProtocolTranslator {}.ApiToRpc (access .Spec .Protocol )
284320 if err != nil {
285321 return nil , cosierr .NonRetryableError (err )
@@ -296,20 +332,40 @@ func newInternalAccessConfig(
296332 svcAcct = access .Spec .ServiceAccountName
297333 }
298334
299- accessConfigsByBucketId , err := generateInternalAccessedBucketConfigs (access , bucketsByName )
300- if err != nil {
301- return nil , err
302- }
303-
304335 d := & internalAccessConfig {
305- AccountName : acctName ,
306336 Protocol : proto ,
307337 AuthenticationType : authType ,
308338 ServiceAccountName : svcAcct ,
309339 Parameters : access .Status .Parameters ,
310340
341+ BucketsByName : bucketsByName ,
342+ }
343+ return d , nil
344+ }
345+
346+ // Parse the access, and generate a new internal grant access config struct for follow-up.
347+ func newInternalGrantAccessConfig (
348+ access * cosiapi.BucketAccess ,
349+ bucketsByName map [string ]* cosiapi.Bucket ,
350+ secretsByName map [string ]* corev1.Secret ,
351+ ) (* internalGrantAccessConfig , error ) {
352+ sharedCfg , err := newInternalAccessConfig (access , bucketsByName )
353+ if err != nil {
354+ return nil , err
355+ }
356+
357+ acctName := "ba-" + string (access .UID ) // DO NOT CHANGE
358+
359+ accessConfigsByBucketId , err := generateInternalAccessedBucketConfigs (access , bucketsByName )
360+ if err != nil {
361+ return nil , err
362+ }
363+
364+ d := & internalGrantAccessConfig {
365+ internalAccessConfig : * sharedCfg ,
366+
367+ AccountName : acctName ,
311368 AccessConfigsByBucketId : accessConfigsByBucketId ,
312- BucketsByName : bucketsByName ,
313369 SecretsByName : secretsByName ,
314370 }
315371 return d , nil
@@ -322,15 +378,15 @@ func newInternalAccessConfig(
322378func generateInternalAccessedBucketConfigs (
323379 access * cosiapi.BucketAccess ,
324380 bucketsByName map [string ]* cosiapi.Bucket ,
325- ) (accessConfigsByBucketId map [string ]bucketAccessConfig , err error ) {
381+ ) (accessConfigsByBucketId map [string ]bucketGrantAccessConfig , err error ) {
326382 errs := []error {}
327383
328384 bucketNamesByClaimName := make (map [string ]string , len (access .Status .AccessedBuckets ))
329385 for _ , ab := range access .Status .AccessedBuckets {
330386 bucketNamesByClaimName [ab .BucketClaimName ] = ab .BucketName
331387 }
332388
333- accessConfigsByBucketId = make (map [string ]bucketAccessConfig , len (access .Spec .BucketClaims ))
389+ accessConfigsByBucketId = make (map [string ]bucketGrantAccessConfig , len (access .Spec .BucketClaims ))
334390 for _ , claimRef := range access .Spec .BucketClaims {
335391 claimName := claimRef .BucketClaimName
336392 bucketName , ok := bucketNamesByClaimName [claimName ]
@@ -361,7 +417,7 @@ func generateInternalAccessedBucketConfigs(
361417 continue
362418 }
363419
364- cfg := bucketAccessConfig {
420+ cfg := bucketGrantAccessConfig {
365421 AccessMode : rpcMode ,
366422 AccessSecretName : claimRef .AccessSecretName ,
367423 }
@@ -376,8 +432,8 @@ func generateInternalAccessedBucketConfigs(
376432 return accessConfigsByBucketId , nil
377433}
378434
379- // Generate bucket access configs for the RPC call.
380- func (d * internalAccessConfig ) RpcAccessedBucketsList () []* cosiproto.DriverGrantBucketAccessRequest_AccessedBucket {
435+ // Generate bucket access configs for the grant RPC call.
436+ func (d * internalGrantAccessConfig ) RpcAccessedBucketsList () []* cosiproto.DriverGrantBucketAccessRequest_AccessedBucket {
381437 out := make ([]* cosiproto.DriverGrantBucketAccessRequest_AccessedBucket , len (d .AccessConfigsByBucketId ))
382438
383439 i := 0
@@ -397,6 +453,42 @@ func (d *internalAccessConfig) RpcAccessedBucketsList() []*cosiproto.DriverGrant
397453 return out
398454}
399455
456+ // Generate bucket access configs for the revoke RPC call.
457+ func (d * internalRevokeAccessConfig ) RpcAccessedBucketsList () []* cosiproto.DriverRevokeBucketAccessRequest_AccessedBucket {
458+ out := make ([]* cosiproto.DriverRevokeBucketAccessRequest_AccessedBucket , len (d .BucketIDs ))
459+
460+ for i , id := range d .BucketIDs {
461+ out [i ] = & cosiproto.DriverRevokeBucketAccessRequest_AccessedBucket {
462+ BucketId : id ,
463+ }
464+ }
465+
466+ return out
467+ }
468+
469+ // Parse the access, and generate a new internal revoke access config struct for follow-up.
470+ // Returns nil if information needed to revoke access is not present.
471+ func newInternalRevokeAccessConfig (
472+ access * cosiapi.BucketAccess ,
473+ bucketsByName map [string ]* cosiapi.Bucket ,
474+ ) (* internalRevokeAccessConfig , error ) {
475+ sharedCfg , err := newInternalAccessConfig (access , bucketsByName )
476+ if err != nil {
477+ return nil , err
478+ }
479+
480+ if access .Status .AccountID == "" {
481+ return nil , nil // cannot revoke without account ID
482+ }
483+
484+ d := & internalRevokeAccessConfig {
485+ internalAccessConfig : * sharedCfg ,
486+
487+ AccessID : access .Status .AccountID ,
488+ }
489+ return d , nil
490+ }
491+
400492// Internal API-domain details about a successfully-granted access.
401493type grantedAccessApiDetails struct {
402494 AccountId string
@@ -451,7 +543,7 @@ func translateDriverGrantBucketAccessResponseToApi(
451543
452544// Even with a granted access response that translates successfully, there could be other errors
453545// related to the granted access not matching what was requested.
454- func validateGrantedAccess (internalCfg * internalAccessConfig , granted * grantedAccessApiDetails ) error {
546+ func validateGrantedAccess (internalCfg * internalGrantAccessConfig , granted * grantedAccessApiDetails ) error {
455547 errs := []error {}
456548
457549 for bucketId := range internalCfg .AccessConfigsByBucketId {
@@ -475,7 +567,7 @@ func validateGrantedAccess(internalCfg *internalAccessConfig, granted *grantedAc
475567// Update BucketAccess Secret(s)'s data fields with credential and bucket info.
476568func (r * BucketAccessReconciler ) updateSecretsWithGrantedInfo (
477569 ctx context.Context ,
478- internalCfg * internalAccessConfig ,
570+ internalCfg * internalGrantAccessConfig ,
479571 granted * grantedAccessApiDetails ,
480572) error {
481573 errs := []error {}
@@ -706,3 +798,46 @@ func setAccessSecretRequiredMetadata(secret *corev1.Secret) {
706798 // TODO: Consider using Secret type to help hint about COSI usage and the object protocol type?
707799 secret .Type = corev1 .SecretTypeOpaque
708800}
801+
802+ func (r * BucketAccessReconciler ) deleteOwnedAccessSecrets (
803+ ctx context.Context , logger logr.Logger ,
804+ access * cosiapi.BucketAccess ,
805+ ) error {
806+ errs := []error {}
807+
808+ for _ , claimRef := range access .Spec .BucketClaims {
809+ secretName := claimRef .AccessSecretName
810+
811+ nsName := types.NamespacedName {
812+ Namespace : access .Namespace ,
813+ Name : secretName ,
814+ }
815+ secret := & corev1.Secret {}
816+ err := r .Get (ctx , nsName , secret )
817+ if err != nil {
818+ if kerrors .IsNotFound (err ) {
819+ logger .V (1 ).Info ("access Secret already deleted" , "secretName" , secretName )
820+ continue
821+ }
822+ errs = append (errs , err ) // retry later
823+ continue
824+ }
825+
826+ if ! metav1 .IsControlledBy (secret , access ) {
827+ logger .V (1 ).Info ("not deleting access Secret not belonging to BucketAccess" , "secretName" , secretName )
828+ // Returning an error here would prevent BucketAccess deletion when a pre-existing
829+ // Secret is specified in a spec.bucketClaims.accessSecretName.
830+ continue
831+ }
832+
833+ if err := r .Delete (ctx , secret ); err != nil {
834+ errs = append (errs , err )
835+ continue
836+ }
837+ }
838+
839+ if len (errs ) > 0 {
840+ return fmt .Errorf ("failed to ensure deletion of one or more access Secrets: %w" , errors .Join (errs ... ))
841+ }
842+ return nil
843+ }
0 commit comments