Skip to content

Commit fd878a5

Browse files
[feat] Support LinodeObjectStorageBucket deletion if forceDeleteBucket is set (#780)
* support (force) deleting buckets * add tests for bucket force deletion * s3 client mock support for tests * more testing * fix templates * "worked on my machine" ™️ * rework finicky chainsaw test
1 parent ad49dff commit fd878a5

23 files changed

+1460
-21
lines changed

api/v1alpha2/linodeobjectstoragebucket_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const (
2929
ACLPublicRead ObjectStorageACL = "public-read"
3030
ACLAuthenticatedRead ObjectStorageACL = "authenticated-read"
3131
ACLPublicReadWrite ObjectStorageACL = "public-read-write"
32+
33+
BucketFinalizer = "linodeobjectstoragebucket.infrastructure.cluster.x-k8s.io"
3234
)
3335

3436
// LinodeObjectStorageBucketSpec defines the desired state of LinodeObjectStorageBucket
@@ -55,6 +57,14 @@ type LinodeObjectStorageBucketSpec struct {
5557
// If not supplied then the credentials of the controller will be used.
5658
// +optional
5759
CredentialsRef *corev1.SecretReference `json:"credentialsRef"`
60+
61+
// AccessKeyRef is a reference to a LinodeObjectStorageBucketKey for the bucket.
62+
// +optional
63+
AccessKeyRef *corev1.ObjectReference `json:"accessKeyRef"`
64+
65+
// ForceDeleteBucket enables the object storage bucket used to be deleted even if it contains objects.
66+
// +optional
67+
ForceDeleteBucket bool `json:"forceDeleteBucket,omitempty"`
5868
}
5969

6070
// LinodeObjectStorageBucketStatus defines the observed state of LinodeObjectStorageBucket

api/v1alpha2/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

clients/clients.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ type LinodeObjectStorageClient interface {
8686
GetObjectStorageKey(ctx context.Context, keyID int) (*linodego.ObjectStorageKey, error)
8787
CreateObjectStorageKey(ctx context.Context, opts linodego.ObjectStorageKeyCreateOptions) (*linodego.ObjectStorageKey, error)
8888
DeleteObjectStorageKey(ctx context.Context, keyID int) error
89+
DeleteObjectStorageBucket(ctx context.Context, regionID, label string) error
8990
}
9091

9192
// LinodeDNSClient defines the methods that interact with Linode's Domains service.
@@ -128,6 +129,10 @@ type S3Client interface {
128129
DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error)
129130
PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error)
130131
HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error)
132+
GetBucketVersioning(ctx context.Context, params *s3.GetBucketVersioningInput, optFns ...func(*s3.Options)) (*s3.GetBucketVersioningOutput, error)
133+
DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error)
134+
ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error)
135+
ListObjectVersions(ctx context.Context, params *s3.ListObjectVersionsInput, f ...func(*s3.Options)) (*s3.ListObjectVersionsOutput, error)
131136
}
132137

133138
type S3PresignClient interface {

cloud/scope/object_storage_bucket.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
"github.com/go-logr/logr"
1010
"sigs.k8s.io/cluster-api/util/patch"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1113

1214
infrav1alpha2 "github.com/linode/cluster-api-provider-linode/api/v1alpha2"
1315
"github.com/linode/cluster-api-provider-linode/clients"
@@ -77,6 +79,16 @@ func NewObjectStorageBucketScope(ctx context.Context, linodeClientConfig ClientC
7779
}, nil
7880
}
7981

82+
// AddFinalizer adds a finalizer if not present and immediately patches the
83+
// object to avoid any race conditions.
84+
func (s *ObjectStorageBucketScope) AddFinalizer(ctx context.Context) error {
85+
if controllerutil.AddFinalizer(s.Bucket, infrav1alpha2.BucketFinalizer) {
86+
return s.Close(ctx)
87+
}
88+
89+
return nil
90+
}
91+
8092
// PatchObject persists the object storage bucket configuration and status.
8193
func (s *ObjectStorageBucketScope) PatchObject(ctx context.Context) error {
8294
return s.PatchHelper.Patch(ctx, s.Bucket)
@@ -86,3 +98,56 @@ func (s *ObjectStorageBucketScope) PatchObject(ctx context.Context) error {
8698
func (s *ObjectStorageBucketScope) Close(ctx context.Context) error {
8799
return s.PatchObject(ctx)
88100
}
101+
102+
// AddAccessKeyRefFinalizer adds a finalizer to the linodeobjectstoragekey referenced in spec.AccessKeyRef.
103+
func (s *ObjectStorageBucketScope) AddAccessKeyRefFinalizer(ctx context.Context, finalizer string) error {
104+
obj, err := s.getAccessKey(ctx)
105+
if err != nil {
106+
return err
107+
}
108+
109+
controllerutil.AddFinalizer(obj, finalizer)
110+
if err := s.Client.Update(ctx, obj); err != nil {
111+
return fmt.Errorf("add linodeobjectstoragekey finalizer %s/%s: %w", s.Bucket.Spec.AccessKeyRef.Namespace, s.Bucket.Spec.AccessKeyRef.Name, err)
112+
}
113+
114+
return nil
115+
}
116+
117+
// RemoveAccessKeyRefFinalizer removes a finalizer from the linodeobjectstoragekey referenced in spec.AccessKeyRef.
118+
func (s *ObjectStorageBucketScope) RemoveAccessKeyRefFinalizer(ctx context.Context, finalizer string) error {
119+
obj, err := s.getAccessKey(ctx)
120+
if err != nil {
121+
return err
122+
}
123+
124+
controllerutil.RemoveFinalizer(obj, finalizer)
125+
if err := s.Client.Update(ctx, obj); err != nil {
126+
return fmt.Errorf("remove linodeobjectstoragekey finalizer %s/%s: %w", s.Bucket.Spec.AccessKeyRef.Namespace, s.Bucket.Spec.AccessKeyRef.Name, err)
127+
}
128+
129+
return nil
130+
}
131+
132+
func (s *ObjectStorageBucketScope) getAccessKey(ctx context.Context) (*infrav1alpha2.LinodeObjectStorageKey, error) {
133+
if s.Bucket.Spec.AccessKeyRef == nil {
134+
return nil, fmt.Errorf("accessKeyRef is nil for bucket %s", s.Bucket.Name)
135+
}
136+
137+
objKeyNamespace := s.Bucket.Spec.AccessKeyRef.Namespace
138+
if s.Bucket.Spec.AccessKeyRef.Namespace == "" {
139+
objKeyNamespace = s.Bucket.Namespace
140+
}
141+
142+
objKey := client.ObjectKey{
143+
Name: s.Bucket.Spec.AccessKeyRef.Name,
144+
Namespace: objKeyNamespace,
145+
}
146+
147+
objStorageKey := &infrav1alpha2.LinodeObjectStorageKey{}
148+
if err := s.Client.Get(ctx, objKey, objStorageKey); err != nil {
149+
return nil, fmt.Errorf("get linodeobjectstoragekey %s: %w", s.Bucket.Spec.AccessKeyRef.Name, err)
150+
}
151+
152+
return objStorageKey, nil
153+
}

0 commit comments

Comments
 (0)