Skip to content

Commit 905cbf3

Browse files
committed
implement BucketAccess Sidecar deletion
Implement BucketAccess deletion in the Sidecar. Signed-off-by: Blaine Gardner <[email protected]>
1 parent ba48797 commit 905cbf3

File tree

1 file changed

+169
-34
lines changed

1 file changed

+169
-34
lines changed

sidecar/internal/reconciler/bucketaccess.go

Lines changed: 169 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
256282
type 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.
276315
func 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(
322378
func 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.
401493
type 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.
476568
func (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

Comments
 (0)