diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go index be53426281..616e60d164 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go +++ b/cmd/clusterawsadm/cloudformation/bootstrap/cluster_api_controller.go @@ -283,6 +283,7 @@ func (t Template) ControllersPolicy() *iamv1.PolicyDocument { "s3:PutObject", "s3:DeleteObject", "s3:PutBucketPolicy", + "s3:PutBucketTagging", }, }) } diff --git a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml index d4c8817892..a045a10bf0 100644 --- a/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml +++ b/cmd/clusterawsadm/cloudformation/bootstrap/fixtures/with_s3_bucket.yaml @@ -288,6 +288,7 @@ Resources: - s3:PutObject - s3:DeleteObject - s3:PutBucketPolicy + - s3:PutBucketTagging Effect: Allow Resource: - arn:*:s3:::cluster-api-provider-aws-* diff --git a/pkg/cloud/services/s3/s3.go b/pkg/cloud/services/s3/s3.go index 96a39f8bcd..8940eda38c 100644 --- a/pkg/cloud/services/s3/s3.go +++ b/pkg/cloud/services/s3/s3.go @@ -31,6 +31,7 @@ import ( "github.com/aws/aws-sdk-go/service/sts/stsiface" "github.com/pkg/errors" + infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2" iam "sigs.k8s.io/cluster-api-provider-aws/v2/iam/api/v1beta1" "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" "sigs.k8s.io/cluster-api-provider-aws/v2/util/system" @@ -68,6 +69,10 @@ func (s *Service) ReconcileBucket() error { return errors.Wrap(err, "ensuring bucket exists") } + if err := s.tagBucket(bucketName); err != nil { + return errors.Wrap(err, "tagging bucket") + } + if err := s.ensureBucketPolicy(bucketName); err != nil { return errors.Wrap(err, "ensuring bucket policy") } @@ -230,6 +235,39 @@ func (s *Service) ensureBucketPolicy(bucketName string) error { return nil } +func (s *Service) tagBucket(bucketName string) error { + taggingInput := &s3.PutBucketTaggingInput{ + Bucket: aws.String(bucketName), + Tagging: &s3.Tagging{ + TagSet: nil, + }, + } + + tags := infrav1.Build(infrav1.BuildParams{ + ClusterName: s.scope.Name(), + Lifecycle: infrav1.ResourceLifecycleOwned, + Name: nil, + Role: aws.String("node"), + Additional: nil, + }) + + for key, value := range tags { + taggingInput.Tagging.TagSet = append(taggingInput.Tagging.TagSet, &s3.Tag{ + Key: aws.String(key), + Value: aws.String(value), + }) + } + + _, err := s.S3Client.PutBucketTagging(taggingInput) + if err != nil { + return err + } + + s.scope.Trace("Tagged bucket", "bucket_name", bucketName) + + return nil +} + func (s *Service) bucketPolicy(bucketName string) (string, error) { accountID, err := s.STSClient.GetCallerIdentity(&sts.GetCallerIdentityInput{}) if err != nil { diff --git a/pkg/cloud/services/s3/s3_test.go b/pkg/cloud/services/s3/s3_test.go index 700dcc3ae3..f3753b24ad 100644 --- a/pkg/cloud/services/s3/s3_test.go +++ b/pkg/cloud/services/s3/s3_test.go @@ -75,6 +75,25 @@ func TestReconcileBucket(t *testing.T) { } s3Mock.EXPECT().CreateBucket(gomock.Eq(input)).Return(nil, nil).Times(1) + + taggingInput := &s3svc.PutBucketTaggingInput{ + Bucket: aws.String(expectedBucketName), + Tagging: &s3svc.Tagging{ + TagSet: []*s3svc.Tag{ + { + Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), + Value: aws.String("owned"), + }, + { + Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), + Value: aws.String("node"), + }, + }, + }, + } + + s3Mock.EXPECT().PutBucketTagging(gomock.Eq(taggingInput)).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(1) if err := svc.ReconcileBucket(); err != nil { @@ -129,6 +148,7 @@ func TestReconcileBucket(t *testing.T) { } }).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(1) if err := svc.ReconcileBucket(); err != nil { @@ -150,6 +170,7 @@ func TestReconcileBucket(t *testing.T) { }) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Do(func(input *s3svc.PutBucketPolicyInput) { if input.Policy == nil { t.Fatalf("Policy must be defined") @@ -189,6 +210,7 @@ func TestReconcileBucket(t *testing.T) { svc, s3Mock := testService(t, &infrav1.S3Bucket{}) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(2) + s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(2) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(2) if err := svc.ReconcileBucket(); err != nil { @@ -208,6 +230,7 @@ func TestReconcileBucket(t *testing.T) { err := awserr.New(s3svc.ErrCodeBucketAlreadyOwnedByYou, "err", errors.New("err")) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, err).Times(1) + s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, nil).Times(1) if err := svc.ReconcileBucket(); err != nil { @@ -248,6 +271,7 @@ func TestReconcileBucket(t *testing.T) { svc, s3Mock := testService(t, &infrav1.S3Bucket{}) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) mockCtrl := gomock.NewController(t) stsMock := mock_stsiface.NewMockSTSAPI(mockCtrl) @@ -265,6 +289,7 @@ func TestReconcileBucket(t *testing.T) { svc, s3Mock := testService(t, &infrav1.S3Bucket{}) s3Mock.EXPECT().CreateBucket(gomock.Any()).Return(nil, nil).Times(1) + s3Mock.EXPECT().PutBucketTagging(gomock.Any()).Return(nil, nil).Times(1) s3Mock.EXPECT().PutBucketPolicy(gomock.Any()).Return(nil, errors.New("error")).Times(1) if err := svc.ReconcileBucket(); err == nil {