diff --git a/tencentcloud/data_source_tc_cos_buckets.go b/tencentcloud/data_source_tc_cos_buckets.go index 9811c18021..4fc7440c62 100644 --- a/tencentcloud/data_source_tc_cos_buckets.go +++ b/tencentcloud/data_source_tc_cos_buckets.go @@ -14,7 +14,9 @@ package tencentcloud import ( "context" + "encoding/xml" "fmt" + "log" "strings" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -271,10 +273,15 @@ func dataSourceTencentCloudCosBuckets() *schema.Resource { }, }, }, + "acl": { + Type: schema.TypeString, + Computed: true, + Description: "Bucket access control configurations.", + }, "acl_body": { Type: schema.TypeString, Computed: true, - Description: "Bucket acl configurations.", + Description: "Bucket verbose acl configurations.", }, "tags": { Type: schema.TypeMap, @@ -366,11 +373,20 @@ func dataSourceTencentCloudCosBucketsRead(d *schema.ResourceData, meta interface bucket["origin_domain_rules"] = domainRules } - aclBody, err := cosService.GetBucketACLXML(ctx, *v.Name) + aclBody, err := cosService.GetBucketACL(ctx, *v.Name) + if err != nil { return err } - bucket["acl_body"] = aclBody + + aclXML, err := xml.Marshal(aclBody) + + if err != nil { + log.Printf("WARN: acl body marshal failed: %s", err.Error()) + } else { + bucket["acl"] = GetBucketPublicACL(aclBody) + bucket["acl_body"] = string(aclXML) + } bucket["tags"] = respTags bucket["cos_bucket_url"] = fmt.Sprintf("%s.cos.%s.myqcloud.com", *v.Name, meta.(*TencentCloudClient).apiV3Conn.Region) diff --git a/tencentcloud/resource_tc_cos_bucket.go b/tencentcloud/resource_tc_cos_bucket.go index f3dbef97a5..5c5ee74ed0 100644 --- a/tencentcloud/resource_tc_cos_bucket.go +++ b/tencentcloud/resource_tc_cos_bucket.go @@ -28,6 +28,7 @@ Using verbose acl ```hcl resource "tencentcloud_cos_bucket" "with_acl_body" { bucket = "mycos-1258798060" + # NOTE: Granting http://cam.qcloud.com/groups/global/AllUsers `READ` Permission is equivalent to "public-read" acl acl_body = < @@ -43,12 +44,14 @@ resource "tencentcloud_cos_bucket" "with_acl_body" { qcs::cam::uin/100000000001:uin/100000000001 + qcs::cam::uin/100000000001:uin/100000000001 WRITE qcs::cam::uin/100000000001:uin/100000000001 + qcs::cam::uin/100000000001:uin/100000000001 READ_ACP @@ -233,8 +236,10 @@ package tencentcloud import ( "bytes" "context" + "encoding/xml" "fmt" "log" + "reflect" "time" "github.com/tencentyun/cos-go-sdk-v5" @@ -404,9 +409,24 @@ func resourceTencentCloudCosBucket() *schema.Resource { Description: "The canned ACL to apply. Valid values: private, public-read, and public-read-write. Defaults to private.", }, "acl_body": { - Type: schema.TypeString, - Optional: true, - Description: "ACL XML body for multiple grant info.", + Type: schema.TypeString, + Optional: true, + + DiffSuppressFunc: func(k, olds, news string, d *schema.ResourceData) bool { + var oldXML cos.BucketGetACLResult + err := xml.Unmarshal([]byte(olds), &oldXML) + if err != nil { + return olds == news + } + var newXML cos.BucketGetACLResult + err = xml.Unmarshal([]byte(news), &newXML) + if err != nil { + return olds == news + } + suppress := reflect.DeepEqual(oldXML, newXML) + return suppress + }, + Description: "ACL XML body for multiple grant info. NOTE: this argument will overwrite `acl`. Check https://intl.cloud.tencent.com/document/product/436/7737 for more detail.", }, "encryption_algorithm": { Type: schema.TypeString, @@ -737,6 +757,26 @@ func resourceTencentCloudCosBucketRead(d *schema.ResourceData, meta interface{}) if err != nil { return err } + + // acl + aclResult, err := cosService.GetBucketACL(ctx, bucket) + + if err != nil { + return err + } + + aclBody, err := xml.Marshal(aclResult) + + if err != nil { + log.Printf("[WARN] Marshal XML Error: %s", err.Error()) + } else if v, ok := d.Get("acl_body").(string); ok && v != "" { + _ = d.Set("acl_body", string(aclBody)) + } + + acl := GetBucketPublicACL(aclResult) + + _ = d.Set("acl", acl) + // read the cors corsRules, err := cosService.GetBucketCors(ctx, bucket) if err != nil { diff --git a/tencentcloud/resource_tc_cos_bucket_test.go b/tencentcloud/resource_tc_cos_bucket_test.go index b2bb5fbd9f..5c986e0df3 100644 --- a/tencentcloud/resource_tc_cos_bucket_test.go +++ b/tencentcloud/resource_tc_cos_bucket_test.go @@ -86,10 +86,44 @@ func TestAccTencentCloudCosBucket_basic(t *testing.T) { ), }, { - ResourceName: "tencentcloud_cos_bucket.bucket_basic", + ResourceName: "tencentcloud_cos_bucket.bucket_basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccTencentCloudCosBucket_ACL(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCosBucket_ACL(appid, ownerUin), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCosBucketExists("tencentcloud_cos_bucket.bucket_acl"), + resource.TestCheckResourceAttr("tencentcloud_cos_bucket.bucket_acl", "acl", "public-read"), + resource.TestCheckResourceAttrSet("tencentcloud_cos_bucket.bucket_acl", "acl_body"), + ), + }, + // test update bucket acl + { + Config: testAccCosBucket_ACLUpdate(appid, ownerUin), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCosBucketExists("tencentcloud_cos_bucket.bucket_acl"), + resource.TestCheckResourceAttr("tencentcloud_cos_bucket.bucket_acl", "acl", "private"), + resource.TestCheckResourceAttrSet("tencentcloud_cos_bucket.bucket_acl", "acl_body"), + ), + }, + { + ResourceName: "tencentcloud_cos_bucket.bucket_acl", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"acl"}, + ImportStateVerifyIgnore: []string{"acl_body"}, }, }, }) @@ -487,6 +521,64 @@ resource "tencentcloud_cos_bucket" "bucket_basic" { `, appid) } +func testAccCosBucket_ACL(appid, uin string) string { + return fmt.Sprintf(` +resource "tencentcloud_cos_bucket" "bucket_acl" { + bucket = "tf-bucket-acl-%s" + acl = "public-read" + acl_body = < + + qcs::cam::uin/%[2]v:uin/%[2]v + qcs::cam::uin/%[2]v:uin/%[2]v + + + + + http://cam.qcloud.com/groups/global/AllUsers + + READ + + + + qcs::cam::uin/%[2]v:uin/%[2]v + qcs::cam::uin/%[2]v:uin/%[2]v + + FULL_CONTROL + + + +EOF +} +`, appid, uin) +} + +func testAccCosBucket_ACLUpdate(appid, uin string) string { + return fmt.Sprintf(` +resource "tencentcloud_cos_bucket" "bucket_acl" { + bucket = "tf-bucket-acl-%s" + acl = "private" + acl_body = < + + qcs::cam::uin/%[2]v:uin/%[2]v + qcs::cam::uin/%[2]v:uin/%[2]v + + + + + qcs::cam::uin/%[2]v:uin/%[2]v + qcs::cam::uin/%[2]v:uin/%[2]v + + FULL_CONTROL + + + +EOF +} +`, appid, uin) +} + func testAccCosBucket_tags(appid string) string { return fmt.Sprintf(` resource "tencentcloud_cos_bucket" "bucket_tags" { diff --git a/tencentcloud/service_tencentcloud_cos.go b/tencentcloud/service_tencentcloud_cos.go index 6a3bcf020c..2a79e07676 100644 --- a/tencentcloud/service_tencentcloud_cos.go +++ b/tencentcloud/service_tencentcloud_cos.go @@ -22,6 +22,8 @@ type CosService struct { client *connectivity.TencentCloudClient } +const PUBLIC_GRANTEE = "http://cam.qcloud.com/groups/global/AllUsers" + func (me *CosService) HeadObject(ctx context.Context, bucket, key string) (info *s3.HeadObjectOutput, errRet error) { logId := getLogId(ctx) @@ -823,7 +825,7 @@ func (me *CosService) DeleteBucketPolicy(ctx context.Context, bucket string) (er return nil } -func (me *CosService) GetBucketACLXML(ctx context.Context, bucket string) (result *string, errRet error) { +func (me *CosService) GetBucketACL(ctx context.Context, bucket string) (result *cos.BucketGetACLResult, errRet error) { logId := getLogId(ctx) defer func() { @@ -834,11 +836,11 @@ func (me *CosService) GetBucketACLXML(ctx context.Context, bucket string) (resul }() ratelimit.Check("TencentcloudCosPutBucketACL") - acl, response, err := me.client.UseTencentCosClient(bucket).Bucket.GetACL(ctx) + acl, _, err := me.client.UseTencentCosClient(bucket).Bucket.GetACL(ctx) if err != nil { errRet = fmt.Errorf("cos [GetBucketACL] error: %s, bucket: %s", err.Error(), bucket) - return nil, errRet + return } aclXML, err := xml.Marshal(acl) @@ -848,12 +850,38 @@ func (me *CosService) GetBucketACLXML(ctx context.Context, bucket string) (resul return nil, errRet } - resp, _ := json.Marshal(response) + log.Printf("[DEBUG]%s api[%s] success, response body:\n%s\n", + logId, "GetBucketACL", aclXML) - log.Printf("[DEBUG]%s api[%s] success, request body response body [%s]\n", - logId, "GetBucketACL", resp) + result = acl + + return +} + +func GetBucketPublicACL(acl *cos.BucketGetACLResult) string { + var publicRead, publicWrite bool + + for i := range acl.AccessControlList { + item := acl.AccessControlList[i] + + if item.Grantee.URI == PUBLIC_GRANTEE && item.Permission == "READ" { + publicRead = true + } + + if item.Grantee.URI == PUBLIC_GRANTEE && item.Permission == "WRITE" { + publicWrite = true + } + } + + if publicRead && !publicWrite { + return s3.ObjectCannedACLPublicRead + } + + if publicRead && publicWrite { + return s3.ObjectCannedACLPublicReadWrite + } - return helper.String(string(aclXML)), nil + return s3.ObjectCannedACLPrivate } func (me *CosService) GetBucketPullOrigin(ctx context.Context, bucket string) (result []map[string]interface{}, errRet error) { diff --git a/website/docs/d/cos_buckets.html.markdown b/website/docs/d/cos_buckets.html.markdown index 8b0b207b36..f4d7554eda 100644 --- a/website/docs/d/cos_buckets.html.markdown +++ b/website/docs/d/cos_buckets.html.markdown @@ -33,7 +33,8 @@ The following arguments are supported: In addition to all arguments above, the following attributes are exported: * `bucket_list` - A list of bucket. Each element contains the following attributes: - * `acl_body` - Bucket acl configurations. + * `acl_body` - Bucket verbose acl configurations. + * `acl` - Bucket access control configurations. * `bucket` - Bucket name, the format likes `-`. * `cors_rules` - A list of CORS rule configurations. * `allowed_headers` - Specifies which headers are allowed. diff --git a/website/docs/r/cos_bucket.html.markdown b/website/docs/r/cos_bucket.html.markdown index 448b64748a..0a13fa0107 100644 --- a/website/docs/r/cos_bucket.html.markdown +++ b/website/docs/r/cos_bucket.html.markdown @@ -37,7 +37,8 @@ Using verbose acl ```hcl resource "tencentcloud_cos_bucket" "with_acl_body" { - bucket = "mycos-1258798060" + bucket = "mycos-1258798060" + # NOTE: Granting http://cam.qcloud.com/groups/global/AllUsers `READ` Permission is equivalent to "public-read" acl acl_body = < @@ -53,12 +54,14 @@ resource "tencentcloud_cos_bucket" "with_acl_body" { qcs::cam::uin/100000000001:uin/100000000001 + qcs::cam::uin/100000000001:uin/100000000001 WRITE qcs::cam::uin/100000000001:uin/100000000001 + qcs::cam::uin/100000000001:uin/100000000001 READ_ACP @@ -234,7 +237,7 @@ resource "tencentcloud_cos_bucket" "mycos" { The following arguments are supported: * `bucket` - (Required, ForceNew) The name of a bucket to be created. Bucket format should be [custom name]-[appid], for example `mycos-1258798060`. -* `acl_body` - (Optional) ACL XML body for multiple grant info. +* `acl_body` - (Optional) ACL XML body for multiple grant info. NOTE: this argument will overwrite `acl`. Check https://intl.cloud.tencent.com/document/product/436/7737 for more detail. * `acl` - (Optional) The canned ACL to apply. Valid values: private, public-read, and public-read-write. Defaults to private. * `cors_rules` - (Optional) A rule of Cross-Origin Resource Sharing (documented below). * `encryption_algorithm` - (Optional) The server-side encryption algorithm to use. Valid value is `AES256`.