Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions tencentcloud/resource_tc_cos_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ resource "tencentcloud_cos_bucket" "mycos" {
acl = "private"
multi_az = true
versioning_enable = true
force_clean = true
}
```

Expand Down Expand Up @@ -388,7 +389,9 @@ func resourceTencentCloudCosBucket() *schema.Resource {
Update: resourceTencentCloudCosBucketUpdate,
Delete: resourceTencentCloudCosBucketDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
State: helper.ImportWithDefaultValue(map[string]interface{}{
"force_clean": false,
}),
},

Schema: map[string]*schema.Schema{
Expand Down Expand Up @@ -441,6 +444,12 @@ func resourceTencentCloudCosBucket() *schema.Resource {
Default: false,
Description: "Enable bucket versioning.",
},
"force_clean": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Force cleanup all objects before delete bucket.",
},
"replica_role": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -1065,10 +1074,12 @@ func resourceTencentCloudCosBucketDelete(d *schema.ResourceData, meta interface{
ctx := context.WithValue(context.TODO(), logIdKey, logId)

bucket := d.Id()
forced := d.Get("force_clean").(bool)
versioned := d.Get("versioning_enable").(bool)
cosService := CosService{
client: meta.(*TencentCloudClient).apiV3Conn,
}
err := cosService.DeleteBucket(ctx, bucket)
err := cosService.DeleteBucket(ctx, bucket, forced, versioned)
if err != nil {
return err
}
Expand Down
11 changes: 7 additions & 4 deletions tencentcloud/resource_tc_cos_bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func testSweepCosBuckets(region string) error {
}
log.Printf("[INFO] deleting cos bucket: %s", bucket)

if err = cosService.DeleteBucket(ctx, bucket); err != nil {
if err = cosService.DeleteBucket(ctx, bucket, true, true); err != nil {
log.Printf("[ERROR] delete bucket %s error: %s", bucket, err.Error())
}
}
Expand Down Expand Up @@ -87,13 +87,15 @@ func TestAccTencentCloudCosBucket_basic(t *testing.T) {
testAccCheckCosBucketExists("tencentcloud_cos_bucket.bucket_basic"),
resource.TestCheckResourceAttr("tencentcloud_cos_bucket.bucket_basic", "encryption_algorithm", "AES256"),
resource.TestCheckResourceAttr("tencentcloud_cos_bucket.bucket_basic", "versioning_enable", "true"),
resource.TestCheckResourceAttr("tencentcloud_cos_bucket.bucket_basic", "force_clean", "true"),
resource.TestCheckResourceAttrSet("tencentcloud_cos_bucket.bucket_basic", "cos_bucket_url"),
),
},
{
ResourceName: "tencentcloud_cos_bucket.bucket_basic",
ImportState: true,
ImportStateVerify: true,
ResourceName: "tencentcloud_cos_bucket.bucket_basic",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"force_clean"},
},
},
})
Expand Down Expand Up @@ -555,6 +557,7 @@ resource "tencentcloud_cos_bucket" "bucket_basic" {
acl = "private"
encryption_algorithm = "AES256"
versioning_enable = true
force_clean = true
}
`, userInfoData)
}
Expand Down
116 changes: 115 additions & 1 deletion tencentcloud/service_tencentcloud_cos.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"

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

Expand Down Expand Up @@ -265,9 +266,18 @@ func (me *CosService) TencentcloudHeadBucket(ctx context.Context, bucket string)
return
}

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

if forced {
log.Printf("[DEBUG]%s api[%s] triggered, bucket [%s], versioned [%v]\n",
logId, "ForceCleanObject", bucket, versioned)
err := me.ForceCleanObject(ctx, bucket, versioned)
if err != nil {
return err
}
}

request := s3.DeleteBucketInput{
Bucket: aws.String(bucket),
}
Expand All @@ -284,6 +294,110 @@ func (me *CosService) DeleteBucket(ctx context.Context, bucket string) (errRet e
return nil
}

func (me *CosService) ForceCleanObject(ctx context.Context, bucket string, versioned bool) error {
logId := getLogId(ctx)

// Get the object list of bucket with all versions
verOpt := cos.BucketGetObjectVersionsOptions{}
objList, resp, err := me.client.UseTencentCosClient(bucket).Bucket.GetObjectVersions(ctx, &verOpt)

if err != nil {
log.Printf("[CRITAL]%s api[%s] fail, resp body [%s], reason[%s]\n",
logId, "GetObjectVersions", resp.Body, err.Error())
return fmt.Errorf("cos force clean object error: %s, bucket: %s", err.Error(), bucket)
}
if objList.IsTruncated {
return fmt.Errorf("cos force clean object error: the list of objects is truncated and the bucket[%s] needs to be deleted manually!!!", bucket)
}

verCnt := len(objList.Version)
markerCnt := len(objList.DeleteMarker)
log.Printf("[DEBUG][ForceCleanObject]%s api[%s] success, get [%v] versions of object, get [%v] deleteMarker, versioned[%v].\n", logId, "GetObjectVersions", verCnt, markerCnt, versioned)

delCnt := verCnt + markerCnt
if delCnt == 0 {
return nil
}

delObjs := make([]cos.Object, 0, delCnt)
if versioned {
//add the versions
for _, v := range objList.Version {
delObjs = append(delObjs, cos.Object{
Key: v.Key,
VersionId: v.VersionId,
})
}
// add the delete-marker
for _, m := range objList.DeleteMarker {
delObjs = append(delObjs, cos.Object{
Key: m.Key,
VersionId: m.VersionId,
})
}
} else {
for _, v := range objList.Version {
delObjs = append(delObjs, cos.Object{
Key: v.Key,
})
}
}

opt := cos.ObjectDeleteMultiOptions{
Quiet: true,
Objects: delObjs,
}

// Multi-delete by specified object.
result, resp, err := me.client.UseTencentCosClient(bucket).Object.DeleteMulti(ctx, &opt)

if err != nil {
log.Printf("[CRITAL]%s api[%s] fail, resp body [%s], reason[%s], opt[%v]\n",
logId, "DeleteMulti", resp.Body, err.Error(), opt)
return fmt.Errorf("cos force clean object error: %s, bucket: %s", err.Error(), bucket)
}
log.Printf("[DEBUG][ForceCleanObject]%s api[%s] completed, removed [%v] versions of object. [%v] failed to remove.\n",
logId, "DeleteMulti", len(result.DeletedObjects), len(result.Errors))

// Clean the failed removal version.
if len(result.Errors) > 0 {
log.Printf("[CRITAL]%s api[%s] it still [%v] objects have not been removed, need try DeleteMulti again.\n",
logId, "DeleteMulti", len(result.Errors))

if err = resource.Retry(readRetryTimeout, func() *resource.RetryError {
unDelObjs := make([]cos.Object, 0, len(result.Errors))
for _, v := range result.Errors {
unDelObjs = append(unDelObjs, cos.Object{
Key: v.Key,
VersionId: v.VersionId,
})
}
unDelOpt := cos.ObjectDeleteMultiOptions{
Quiet: true,
Objects: unDelObjs,
}

result, resp, err := me.client.UseTencentCosClient(bucket).Object.DeleteMulti(ctx, &unDelOpt)
if err != nil {
log.Printf("[CRITAL][retry]%s api[%s] fail, resp body [%s], reason[%s]\n",
logId, "DeleteMulti ", resp.Body, err.Error())
return retryError(err, InternalError)
}
if len(result.Errors) > 0 {
return resource.RetryableError(fmt.Errorf("[CRITAL][retry]%s api[%s] it still %v objects have not been removed, need try DeleteMulti again.\n",
logId, "DeleteMulti", len(result.Errors)))
}
return nil
}); err != nil {
return err
}
}

log.Printf("[DEBUG][ForceCleanObject]%s api[%s] success, [%v] objects have been cleaned.\n",
logId, "ForceCleanObject", len(result.DeletedObjects))
return nil
}

func (me *CosService) GetBucketCors(ctx context.Context, bucket string) (corsRules []map[string]interface{}, errRet error) {
logId := getLogId(ctx)

Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/cos_bucket.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ resource "tencentcloud_cos_bucket" "mycos" {
acl = "private"
multi_az = true
versioning_enable = true
force_clean = true
}
```

Expand Down Expand Up @@ -241,6 +242,7 @@ The following arguments are supported:
* `acl` - (Optional, String) The canned ACL to apply. Valid values: private, public-read, and public-read-write. Defaults to private.
* `cors_rules` - (Optional, List) A rule of Cross-Origin Resource Sharing (documented below).
* `encryption_algorithm` - (Optional, String) The server-side encryption algorithm to use. Valid value is `AES256`.
* `force_clean` - (Optional, Bool) Force cleanup all objects before delete bucket.
* `lifecycle_rules` - (Optional, List) A configuration of object lifecycle management (documented below).
* `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).
* `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`.
Expand Down