Skip to content

Commit 63c6635

Browse files
authored
Merge pull request #4904 from r4f4/preserve-ignition
✨ s3: allow best effort delete for objects
2 parents 0977141 + 47e42e1 commit 63c6635

File tree

7 files changed

+69
-9
lines changed

7 files changed

+69
-9
lines changed

api/v1beta1/zz_generated.conversion.go

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

api/v1beta2/awscluster_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,10 @@ type S3Bucket struct {
309309
// +kubebuilder:validation:MaxLength:=63
310310
// +kubebuilder:validation:Pattern=`^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$`
311311
Name string `json:"name"`
312+
313+
// BestEffortDeleteObjects defines whether access/permission errors during object deletion should be ignored.
314+
// +optional
315+
BestEffortDeleteObjects *bool `json:"bestEffortDeleteObjects,omitempty"`
312316
}
313317

314318
// +kubebuilder:object:root=true

api/v1beta2/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.

config/crd/bases/infrastructure.cluster.x-k8s.io_awsclusters.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,10 @@ spec:
16661666
(https://coreos.github.io/ignition/) for bootstrapping (requires
16671667
BootstrapFormatIgnition feature flag to be enabled).
16681668
properties:
1669+
bestEffortDeleteObjects:
1670+
description: BestEffortDeleteObjects defines whether access/permission
1671+
errors during object deletion should be ignored.
1672+
type: boolean
16691673
controlPlaneIAMInstanceProfile:
16701674
description: |-
16711675
ControlPlaneIAMInstanceProfile is a name of the IAMInstanceProfile, which will be allowed

config/crd/bases/infrastructure.cluster.x-k8s.io_awsclustertemplates.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,10 @@ spec:
12661266
(https://coreos.github.io/ignition/) for bootstrapping (requires
12671267
BootstrapFormatIgnition feature flag to be enabled).
12681268
properties:
1269+
bestEffortDeleteObjects:
1270+
description: BestEffortDeleteObjects defines whether access/permission
1271+
errors during object deletion should be ignored.
1272+
type: boolean
12691273
controlPlaneIAMInstanceProfile:
12701274
description: |-
12711275
ControlPlaneIAMInstanceProfile is a name of the IAMInstanceProfile, which will be allowed

pkg/cloud/services/s3/s3.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/aws/aws-sdk-go/service/sts"
3333
"github.com/aws/aws-sdk-go/service/sts/stsiface"
3434
"github.com/pkg/errors"
35+
"k8s.io/utils/ptr"
3536

3637
infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
3738
iam "sigs.k8s.io/cluster-api-provider-aws/v2/iam/api/v1beta1"
@@ -194,12 +195,8 @@ func (s *Service) Delete(m *scope.MachineScope) error {
194195
// anyway for backwards compatibility reasons.
195196
s.scope.Debug("Received 403 forbidden from S3 HeadObject call. If GetObject permission has been granted to the controller but not ListBucket, object is already deleted. Attempting deletion anyway in case GetObject permission hasn't been granted to the controller but DeleteObject has.", "bucket", bucket, "key", key)
196197

197-
_, err = s.S3Client.DeleteObject(&s3.DeleteObjectInput{
198-
Bucket: aws.String(bucket),
199-
Key: aws.String(key),
200-
})
201-
if err != nil {
202-
return errors.Wrap(err, "deleting S3 object")
198+
if err := s.deleteObject(bucket, key); err != nil {
199+
return err
203200
}
204201

205202
s.scope.Debug("Delete object call succeeded despite missing GetObject permission", "bucket", bucket, "key", key)
@@ -221,11 +218,23 @@ func (s *Service) Delete(m *scope.MachineScope) error {
221218

222219
s.scope.Info("Deleting S3 object", "bucket", bucket, "key", key)
223220

224-
_, err = s.S3Client.DeleteObject(&s3.DeleteObjectInput{
221+
return s.deleteObject(bucket, key)
222+
}
223+
224+
func (s *Service) deleteObject(bucket, key string) error {
225+
if _, err := s.S3Client.DeleteObject(&s3.DeleteObjectInput{
225226
Bucket: aws.String(bucket),
226227
Key: aws.String(key),
227-
})
228-
if err != nil {
228+
}); err != nil {
229+
if ptr.Deref(s.scope.Bucket().BestEffortDeleteObjects, false) {
230+
if aerr, ok := err.(awserr.Error); ok {
231+
switch aerr.Code() {
232+
case "Forbidden", "AccessDenied":
233+
s.scope.Debug("Ignoring deletion error", "bucket", bucket, "key", key, "error", aerr.Message())
234+
return nil
235+
}
236+
}
237+
}
229238
return errors.Wrap(err, "deleting S3 object")
230239
}
231240

pkg/cloud/services/s3/s3_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,18 @@ func TestDeleteObject(t *testing.T) {
738738
t.Fatalf("Unexpected error, got: %v", err)
739739
}
740740
})
741+
742+
t.Run("object_access_denied_and_BestEffortDeleteObjects_is_on", func(t *testing.T) {
743+
t.Parallel()
744+
745+
svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{BestEffortDeleteObjects: aws.Bool(true)}})
746+
s3Mock.EXPECT().HeadObject(gomock.Any()).Return(nil, nil)
747+
s3Mock.EXPECT().DeleteObject(gomock.Any()).Return(nil, awserr.New("AccessDenied", "Access Denied", nil))
748+
749+
if err := svc.Delete(machineScope); err != nil {
750+
t.Fatalf("Unexpected error, got: %v", err)
751+
}
752+
})
741753
})
742754

743755
t.Run("returns_error_when", func(t *testing.T) {
@@ -793,6 +805,27 @@ func TestDeleteObject(t *testing.T) {
793805
t.Fatalf("Expected error")
794806
}
795807
})
808+
809+
t.Run("object_access_denied_and_BestEffortDeleteObjects_is_off", func(t *testing.T) {
810+
t.Parallel()
811+
812+
svc, s3Mock := testService(t, &testServiceInput{Bucket: &infrav1.S3Bucket{}})
813+
s3Mock.EXPECT().HeadObject(gomock.Any()).Return(nil, nil)
814+
s3Mock.EXPECT().DeleteObject(gomock.Any()).Return(nil, awserr.New("AccessDenied", "Access Denied", nil))
815+
816+
machineScope := &scope.MachineScope{
817+
Machine: &clusterv1.Machine{},
818+
AWSMachine: &infrav1.AWSMachine{
819+
ObjectMeta: metav1.ObjectMeta{
820+
Name: nodeName,
821+
},
822+
},
823+
}
824+
825+
if err := svc.Delete(machineScope); err == nil {
826+
t.Fatalf("Expected error")
827+
}
828+
})
796829
})
797830

798831
t.Run("is_idempotent", func(t *testing.T) {

0 commit comments

Comments
 (0)