Skip to content

Commit bd4a434

Browse files
authored
support force clean for cos bucket (#1290)
* support force clean for cos bucket * modify test case * add ResourceImporter for force_clean Co-authored-by: nickyinluo <nickyinluo@tencent.com>
1 parent 2e72516 commit bd4a434

File tree

4 files changed

+137
-7
lines changed

4 files changed

+137
-7
lines changed

tencentcloud/resource_tc_cos_bucket.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ resource "tencentcloud_cos_bucket" "mycos" {
2121
acl = "private"
2222
multi_az = true
2323
versioning_enable = true
24+
force_clean = true
2425
}
2526
```
2627
@@ -388,7 +389,9 @@ func resourceTencentCloudCosBucket() *schema.Resource {
388389
Update: resourceTencentCloudCosBucketUpdate,
389390
Delete: resourceTencentCloudCosBucketDelete,
390391
Importer: &schema.ResourceImporter{
391-
State: schema.ImportStatePassthrough,
392+
State: helper.ImportWithDefaultValue(map[string]interface{}{
393+
"force_clean": false,
394+
}),
392395
},
393396

394397
Schema: map[string]*schema.Schema{
@@ -441,6 +444,12 @@ func resourceTencentCloudCosBucket() *schema.Resource {
441444
Default: false,
442445
Description: "Enable bucket versioning.",
443446
},
447+
"force_clean": {
448+
Type: schema.TypeBool,
449+
Optional: true,
450+
Default: false,
451+
Description: "Force cleanup all objects before delete bucket.",
452+
},
444453
"replica_role": {
445454
Type: schema.TypeString,
446455
Optional: true,
@@ -1065,10 +1074,12 @@ func resourceTencentCloudCosBucketDelete(d *schema.ResourceData, meta interface{
10651074
ctx := context.WithValue(context.TODO(), logIdKey, logId)
10661075

10671076
bucket := d.Id()
1077+
forced := d.Get("force_clean").(bool)
1078+
versioned := d.Get("versioning_enable").(bool)
10681079
cosService := CosService{
10691080
client: meta.(*TencentCloudClient).apiV3Conn,
10701081
}
1071-
err := cosService.DeleteBucket(ctx, bucket)
1082+
err := cosService.DeleteBucket(ctx, bucket, forced, versioned)
10721083
if err != nil {
10731084
return err
10741085
}

tencentcloud/resource_tc_cos_bucket_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func testSweepCosBuckets(region string) error {
5959
}
6060
log.Printf("[INFO] deleting cos bucket: %s", bucket)
6161

62-
if err = cosService.DeleteBucket(ctx, bucket); err != nil {
62+
if err = cosService.DeleteBucket(ctx, bucket, true, true); err != nil {
6363
log.Printf("[ERROR] delete bucket %s error: %s", bucket, err.Error())
6464
}
6565
}
@@ -87,13 +87,15 @@ func TestAccTencentCloudCosBucket_basic(t *testing.T) {
8787
testAccCheckCosBucketExists("tencentcloud_cos_bucket.bucket_basic"),
8888
resource.TestCheckResourceAttr("tencentcloud_cos_bucket.bucket_basic", "encryption_algorithm", "AES256"),
8989
resource.TestCheckResourceAttr("tencentcloud_cos_bucket.bucket_basic", "versioning_enable", "true"),
90+
resource.TestCheckResourceAttr("tencentcloud_cos_bucket.bucket_basic", "force_clean", "true"),
9091
resource.TestCheckResourceAttrSet("tencentcloud_cos_bucket.bucket_basic", "cos_bucket_url"),
9192
),
9293
},
9394
{
94-
ResourceName: "tencentcloud_cos_bucket.bucket_basic",
95-
ImportState: true,
96-
ImportStateVerify: true,
95+
ResourceName: "tencentcloud_cos_bucket.bucket_basic",
96+
ImportState: true,
97+
ImportStateVerify: true,
98+
ImportStateVerifyIgnore: []string{"force_clean"},
9799
},
98100
},
99101
})
@@ -555,6 +557,7 @@ resource "tencentcloud_cos_bucket" "bucket_basic" {
555557
acl = "private"
556558
encryption_algorithm = "AES256"
557559
versioning_enable = true
560+
force_clean = true
558561
}
559562
`, userInfoData)
560563
}

tencentcloud/service_tencentcloud_cos.go

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111

1212
"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
13+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
1314

1415
"github.com/tencentyun/cos-go-sdk-v5"
1516

@@ -265,9 +266,18 @@ func (me *CosService) TencentcloudHeadBucket(ctx context.Context, bucket string)
265266
return
266267
}
267268

268-
func (me *CosService) DeleteBucket(ctx context.Context, bucket string) (errRet error) {
269+
func (me *CosService) DeleteBucket(ctx context.Context, bucket string, forced bool, versioned bool) (errRet error) {
269270
logId := getLogId(ctx)
270271

272+
if forced {
273+
log.Printf("[DEBUG]%s api[%s] triggered, bucket [%s], versioned [%v]\n",
274+
logId, "ForceCleanObject", bucket, versioned)
275+
err := me.ForceCleanObject(ctx, bucket, versioned)
276+
if err != nil {
277+
return err
278+
}
279+
}
280+
271281
request := s3.DeleteBucketInput{
272282
Bucket: aws.String(bucket),
273283
}
@@ -284,6 +294,110 @@ func (me *CosService) DeleteBucket(ctx context.Context, bucket string) (errRet e
284294
return nil
285295
}
286296

297+
func (me *CosService) ForceCleanObject(ctx context.Context, bucket string, versioned bool) error {
298+
logId := getLogId(ctx)
299+
300+
// Get the object list of bucket with all versions
301+
verOpt := cos.BucketGetObjectVersionsOptions{}
302+
objList, resp, err := me.client.UseTencentCosClient(bucket).Bucket.GetObjectVersions(ctx, &verOpt)
303+
304+
if err != nil {
305+
log.Printf("[CRITAL]%s api[%s] fail, resp body [%s], reason[%s]\n",
306+
logId, "GetObjectVersions", resp.Body, err.Error())
307+
return fmt.Errorf("cos force clean object error: %s, bucket: %s", err.Error(), bucket)
308+
}
309+
if objList.IsTruncated {
310+
return fmt.Errorf("cos force clean object error: the list of objects is truncated and the bucket[%s] needs to be deleted manually!!!", bucket)
311+
}
312+
313+
verCnt := len(objList.Version)
314+
markerCnt := len(objList.DeleteMarker)
315+
log.Printf("[DEBUG][ForceCleanObject]%s api[%s] success, get [%v] versions of object, get [%v] deleteMarker, versioned[%v].\n", logId, "GetObjectVersions", verCnt, markerCnt, versioned)
316+
317+
delCnt := verCnt + markerCnt
318+
if delCnt == 0 {
319+
return nil
320+
}
321+
322+
delObjs := make([]cos.Object, 0, delCnt)
323+
if versioned {
324+
//add the versions
325+
for _, v := range objList.Version {
326+
delObjs = append(delObjs, cos.Object{
327+
Key: v.Key,
328+
VersionId: v.VersionId,
329+
})
330+
}
331+
// add the delete-marker
332+
for _, m := range objList.DeleteMarker {
333+
delObjs = append(delObjs, cos.Object{
334+
Key: m.Key,
335+
VersionId: m.VersionId,
336+
})
337+
}
338+
} else {
339+
for _, v := range objList.Version {
340+
delObjs = append(delObjs, cos.Object{
341+
Key: v.Key,
342+
})
343+
}
344+
}
345+
346+
opt := cos.ObjectDeleteMultiOptions{
347+
Quiet: true,
348+
Objects: delObjs,
349+
}
350+
351+
// Multi-delete by specified object.
352+
result, resp, err := me.client.UseTencentCosClient(bucket).Object.DeleteMulti(ctx, &opt)
353+
354+
if err != nil {
355+
log.Printf("[CRITAL]%s api[%s] fail, resp body [%s], reason[%s], opt[%v]\n",
356+
logId, "DeleteMulti", resp.Body, err.Error(), opt)
357+
return fmt.Errorf("cos force clean object error: %s, bucket: %s", err.Error(), bucket)
358+
}
359+
log.Printf("[DEBUG][ForceCleanObject]%s api[%s] completed, removed [%v] versions of object. [%v] failed to remove.\n",
360+
logId, "DeleteMulti", len(result.DeletedObjects), len(result.Errors))
361+
362+
// Clean the failed removal version.
363+
if len(result.Errors) > 0 {
364+
log.Printf("[CRITAL]%s api[%s] it still [%v] objects have not been removed, need try DeleteMulti again.\n",
365+
logId, "DeleteMulti", len(result.Errors))
366+
367+
if err = resource.Retry(readRetryTimeout, func() *resource.RetryError {
368+
unDelObjs := make([]cos.Object, 0, len(result.Errors))
369+
for _, v := range result.Errors {
370+
unDelObjs = append(unDelObjs, cos.Object{
371+
Key: v.Key,
372+
VersionId: v.VersionId,
373+
})
374+
}
375+
unDelOpt := cos.ObjectDeleteMultiOptions{
376+
Quiet: true,
377+
Objects: unDelObjs,
378+
}
379+
380+
result, resp, err := me.client.UseTencentCosClient(bucket).Object.DeleteMulti(ctx, &unDelOpt)
381+
if err != nil {
382+
log.Printf("[CRITAL][retry]%s api[%s] fail, resp body [%s], reason[%s]\n",
383+
logId, "DeleteMulti ", resp.Body, err.Error())
384+
return retryError(err, InternalError)
385+
}
386+
if len(result.Errors) > 0 {
387+
return resource.RetryableError(fmt.Errorf("[CRITAL][retry]%s api[%s] it still %v objects have not been removed, need try DeleteMulti again.\n",
388+
logId, "DeleteMulti", len(result.Errors)))
389+
}
390+
return nil
391+
}); err != nil {
392+
return err
393+
}
394+
}
395+
396+
log.Printf("[DEBUG][ForceCleanObject]%s api[%s] success, [%v] objects have been cleaned.\n",
397+
logId, "ForceCleanObject", len(result.DeletedObjects))
398+
return nil
399+
}
400+
287401
func (me *CosService) GetBucketCors(ctx context.Context, bucket string) (corsRules []map[string]interface{}, errRet error) {
288402
logId := getLogId(ctx)
289403

website/docs/r/cos_bucket.html.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ resource "tencentcloud_cos_bucket" "mycos" {
3030
acl = "private"
3131
multi_az = true
3232
versioning_enable = true
33+
force_clean = true
3334
}
3435
```
3536

@@ -241,6 +242,7 @@ The following arguments are supported:
241242
* `acl` - (Optional, String) The canned ACL to apply. Valid values: private, public-read, and public-read-write. Defaults to private.
242243
* `cors_rules` - (Optional, List) A rule of Cross-Origin Resource Sharing (documented below).
243244
* `encryption_algorithm` - (Optional, String) The server-side encryption algorithm to use. Valid value is `AES256`.
245+
* `force_clean` - (Optional, Bool) Force cleanup all objects before delete bucket.
244246
* `lifecycle_rules` - (Optional, List) A configuration of object lifecycle management (documented below).
245247
* `log_enable` - (Optional, Bool) Indicate the access log of this bucket to be saved or not. Default is `false`. If set `true`, the access log will be saved with `log_target_bucket`. To enable log, the full access of log service must be granted. [Full Access Role Policy](https://intl.cloud.tencent.com/document/product/436/16920).
246248
* `log_prefix` - (Optional, String) The prefix log name which saves the access log of this bucket per 5 minutes. Eg. `MyLogPrefix/`. The log access file format is `log_target_bucket`/`log_prefix`{YYYY}/{MM}/{DD}/{time}_{random}_{index}.gz. Only valid when `log_enable` is `true`.

0 commit comments

Comments
 (0)