diff --git a/docs/data-sources/obs_bucket_object.md b/docs/data-sources/obs_bucket_object.md new file mode 100644 index 00000000..a2d27d24 --- /dev/null +++ b/docs/data-sources/obs_bucket_object.md @@ -0,0 +1,48 @@ +--- +subcategory: "Object Storage Service (OBS)" +--- + +# flexibleengine_obs_bucket_object + +Use this data source to get info of special FlexibleEngine obs object. + +```hcl +data "flexibleengine_obs_bucket_object" "object" { + bucket = "my-test-bucket" + key = "new-key" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String) The region in which to obtain the OBS object. If omitted, the provider-level region will + be used. + +* `bucket` - (Required, String) The name of the bucket to put the file in. + +* `key` - (Required, String) The name of the object once it is in the bucket. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - the `key` of the resource supplied above. + +* `etag` - the ETag generated for the object (an MD5 sum of the object content). When the object is encrypted on the + server side, the ETag value is not the MD5 value of the object, but the unique identifier calculated through the + server-side encryption. + +* `size` - the size of the object in bytes. + +* `version_id` - a unique version ID value for the object, if bucket versioning is enabled. + +* `storage_class` - specifies the storage class of the object. + +* `content_type` - a standard MIME type describing the format of the object data, e.g. application/octet-stream. All + Valid MIME Types are valid for this input. + +* `body` - The content of an object which is available only for objects which have a human-readable Content-Type + (text/* and application/json) and smaller than **64KB**. This is to prevent printing unsafe characters and + potentially downloading large amount of data. diff --git a/docs/data-sources/obs_buckets.md b/docs/data-sources/obs_buckets.md new file mode 100644 index 00000000..a8b92e52 --- /dev/null +++ b/docs/data-sources/obs_buckets.md @@ -0,0 +1,44 @@ +--- +subcategory: "Object Storage Service (OBS)" +--- + +# flexibleengine_obs_buckets + +Use this data source to get all OBS buckets. + +```hcl +data "flexibleengine_obs_buckets" "buckets" { + bucket = "your-bucket-name" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String) The region in which to obtain the OBS bucket. + If omitted, the provider-level region will be used. + +* `bucket` - (Optional, String) The name of the OBS bucket. + +* `enterprise_project_id` - (Optional, String) The enterprise project id of the OBS bucket. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the list. + +* `buckets` - A list of OBS buckets. + +The `buckets` block supports: + +* `region` - The region where the OBS bucket belongs. + +* `bucket` - The name of the OBS bucket. + +* `enterprise_project_id` - The enterprise project id of the OBS bucket. + +* `storage_class` - The storage class of the OBS bucket. + +* `created_at` - The date when the OBS bucket was created. diff --git a/docs/resources/obs_bucket_object_acl.md b/docs/resources/obs_bucket_object_acl.md new file mode 100644 index 00000000..e628fffa --- /dev/null +++ b/docs/resources/obs_bucket_object_acl.md @@ -0,0 +1,104 @@ +--- +subcategory: "Object Storage Service (OBS)" +--- + +# flexibleengine_obs_bucket_object_acl + +Manages an OBS bucket object acl resource within FlexibleEngine. + +-> **NOTE:** When creating or updating the OBS bucket object acl, the original object acl will be overwritten. When +deleting the OBS bucket object acl, only the owner permissions will be retained, and the other permissions will be +removed. + +## Example Usage + +```hcl +variable "bucket" {} +variable "key" {} +variable "account1" {} +variable "account2" {} + +resource "flexibleengine_obs_bucket_object_acl" "test" { + bucket = var.bucket + key = var.key + + account_permission { + access_to_object = ["READ"] + access_to_acl = ["READ_ACP", "WRITE_ACP"] + account_id = var.account1 + } + + account_permission { + access_to_object = ["READ"] + access_to_acl = ["READ_ACP"] + account_id = var.account2 + } + + public_permission { + access_to_acl = ["READ_ACP", "WRITE_ACP"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `region` - (Optional, String, ForceNew) Specifies the region in which to create the resource. + If omitted, the provider-level region will be used. + + Changing this parameter will create a new resource. + +* `bucket` - (Required, String, ForceNew) Specifies the name of the bucket which the object belongs to. + + Changing this parameter will create a new resource. + +* `key` - (Required, String, ForceNew) Specifies the name of the object to which to set the acl. + + Changing this parameter will create a new resource. + +* `public_permission` - (Optional, List) Specifies the object public permission. + The [permission_struct](#OBSBucketObjectAcl_permission_struct) structure is documented below. + +* `account_permission` - (Optional, List) Specifies the object account permissions. + The [account_permission_struct](#OBSBucketObjectAcl_account_permission_struct) structure is documented below. + + +The `permission_struct` block supports: + +* `access_to_object` - (Optional, List) Specifies the access to object. Only **READ** supported. + +* `access_to_acl` - (Optional, List) Specifies the access to acl. Valid values are **READ_ACP** and **WRITE_ACP**. + + +The `account_permission_struct` block supports: + +* `account_id` - (Required, String) Specifies the account id to authorize. The account id cannot be the object owner, + and must be unique. + +* `access_to_object` - (Optional, List) Specifies the access to object. Only **READ** supported. + +* `access_to_acl` - (Optional, List) Specifies the access to acl. Valid values are **READ_ACP** and **WRITE_ACP**. + +## Attribute Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The name of the bucket object key. +* `owner_permission` - The object owner permission information. + The [owner_permission_struct](#OBSBucketObjectAcl_owner_permission_struct) structure is documented below. + + +The `owner_permission_struct` block supports: + +* `access_to_object` - The owner object permissions. + +* `access_to_acl` - The owner acl permissions. + +## Import + +The obs bucket object acl can be imported using `bucket` and `key`, separated by a slash, e.g. + +```bash +$ terraform import flexibleengine_obs_bucket_object_acl.test / +``` diff --git a/docs/resources/obs_bucket_policy.md b/docs/resources/obs_bucket_policy.md new file mode 100644 index 00000000..8497afb6 --- /dev/null +++ b/docs/resources/obs_bucket_policy.md @@ -0,0 +1,103 @@ +--- +subcategory: "Object Storage Service (OBS)" +--- + +# flexibleengine_obs_bucket_policy + +Attaches a policy to an OBS bucket resource. + +-> **NOTE:** When creating or updating the OBS bucket policy, the original policy will be overwritten. + +## Example Usage + +### Policy with OBS format + +```hcl +resource "flexibleengine_obs_bucket" "bucket" { + bucket = "my-test-bucket" +} + +resource "flexibleengine_obs_bucket_policy" "policy" { + bucket = flexibleengine_obs_bucket.bucket.id + policy = <`, e.g. + +``` +$ terraform import flexibleengine_obs_bucket_policy.policy +``` + +S3 foramt bucket policy can be imported using the `` and "s3" by a slash, e.g. + +``` +$ terraform import flexibleengine_obs_bucket_policy.s3_policy /s3 +``` diff --git a/flexibleengine/acceptance/acceptance.go b/flexibleengine/acceptance/acceptance.go index 265460d8..13d8ef30 100644 --- a/flexibleengine/acceptance/acceptance.go +++ b/flexibleengine/acceptance/acceptance.go @@ -21,6 +21,7 @@ var ( OS_ACCESS_KEY = os.Getenv("OS_ACCESS_KEY") OS_SECRET_KEY = os.Getenv("OS_SECRET_KEY") OS_PROJECT_ID = os.Getenv("OS_PROJECT_ID") + OS_DOMAIN_ID = os.Getenv("OS_DOMAIN_ID") OS_VPC_ID = os.Getenv("OS_VPC_ID") OS_NETWORK_ID = os.Getenv("OS_NETWORK_ID") @@ -155,3 +156,9 @@ func testAccPreCheckImsBackupId(t *testing.T) { t.Skip("OS_IMS_BACKUP_ID must be set for IMS whole image with CBR backup id") } } + +func testAccPrecheckDomainId(t *testing.T) { + if OS_DOMAIN_ID == "" { + t.Skip("OS_DOMAIN_ID must be set for acceptance tests") + } +} diff --git a/flexibleengine/acceptance/data_source_flexibleengine_obs_bucket_object_test.go b/flexibleengine/acceptance/data_source_flexibleengine_obs_bucket_object_test.go new file mode 100644 index 00000000..10b1e789 --- /dev/null +++ b/flexibleengine/acceptance/data_source_flexibleengine_obs_bucket_object_test.go @@ -0,0 +1,289 @@ +package acceptance + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/chnsz/golangsdk/openstack/obs" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/config" + "github.com/huaweicloud/terraform-provider-huaweicloud/huaweicloud/services/acceptance" +) + +func TestAccObsBucketObjectDataSource_content(t *testing.T) { + rInt := acctest.RandInt() + dataSourceName := "data.flexibleengine_obs_bucket_object.obj" + resourceConf, dataSourceConf := testAccObsBucketObjectDataSource_content(rInt) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckOBS(t) + }, + ProviderFactories: TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: resourceConf, + Check: resource.ComposeTestCheckFunc( + testAccCheckObsBucketObjectExists("flexibleengine_obs_bucket_object.object"), + ), + }, + { + Config: dataSourceConf, + Check: resource.ComposeTestCheckFunc( + testAccCheckObsObjectDataSourceExists(dataSourceName), + resource.TestCheckResourceAttr(dataSourceName, "content_type", "binary/octet-stream"), + resource.TestCheckResourceAttr(dataSourceName, "storage_class", "STANDARD"), + ), + }, + }, + }) +} + +func TestAccObsBucketObjectDataSource_source(t *testing.T) { + dataSourceName := "data.flexibleengine_obs_bucket_object.obj" + tmpFile, err := os.CreateTemp("", "tf-acc-obs-obj-source") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + + // write test data to the tempfile + for i := 0; i < 1024; i++ { + _, err := tmpFile.WriteString("test obs object file storage\n") + if err != nil { + t.Fatal(err) + } + } + tmpFile.Close() + + rInt := acctest.RandInt() + resourceConf, dataSourceConf := testAccObsBucketObjectDataSource_source(rInt, tmpFile.Name()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckOBS(t) + }, + ProviderFactories: TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: resourceConf, + Check: resource.ComposeTestCheckFunc( + testAccCheckObsBucketObjectExists("flexibleengine_obs_bucket_object.object"), + ), + }, + { + Config: dataSourceConf, + Check: resource.ComposeTestCheckFunc( + testAccCheckObsObjectDataSourceExists(dataSourceName), + resource.TestCheckResourceAttr(dataSourceName, "content_type", "binary/octet-stream"), + resource.TestCheckResourceAttr(dataSourceName, "storage_class", "STANDARD"), + ), + }, + }, + }) +} + +func TestAccObsBucketObjectDataSource_allParams(t *testing.T) { + rInt := acctest.RandInt() + dataSourceName := "data.flexibleengine_obs_bucket_object.obj" + resourceConf, dataSourceConf := testAccObsBucketObjectDataSource_allParams(rInt) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckOBS(t) + }, + ProviderFactories: TestAccProviderFactories, + Steps: []resource.TestStep{ + { + Config: resourceConf, + Check: resource.ComposeTestCheckFunc( + testAccCheckObsBucketObjectExists("flexibleengine_obs_bucket_object.object"), + ), + }, + { + Config: dataSourceConf, + Check: resource.ComposeTestCheckFunc( + testAccCheckObsObjectDataSourceExists(dataSourceName), + resource.TestCheckResourceAttr(dataSourceName, "content_type", "application/json"), + resource.TestCheckResourceAttr(dataSourceName, "storage_class", "STANDARD"), + resource.TestCheckResourceAttrSet(dataSourceName, "body"), + ), + }, + }, + }) +} + +func testAccCheckObsBucketObjectExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not Found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No OBS Bucket Object ID is set") + } + + conf := testAccProvider.Meta().(*config.Config) + obsClient, err := conf.ObjectStorageClient(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating OBS client: %s", err) + } + + bucket := rs.Primary.Attributes["bucket"] + key := rs.Primary.Attributes["key"] + input := &obs.ListObjectsInput{} + input.Bucket = bucket + input.Prefix = key + + resp, err := obsClient.ListObjects(input) + if err != nil { + return fmt.Errorf("Error listing objects of OBS bucket %s: %s", bucket, err) + } + + var exist bool + for _, content := range resp.Contents { + if key == content.Key { + exist = true + break + } + } + if !exist { + return fmt.Errorf("Resource %s not found in bucket %s", rs.Primary.ID, bucket) + } + + return nil + } +} + + +func testAccCheckObsObjectDataSourceExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("can't find OBS object data source: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("OBS object data source ID not set") + } + + bucket := rs.Primary.Attributes["bucket"] + key := rs.Primary.Attributes["key"] + + conf := acceptance.TestAccProvider.Meta().(*config.Config) + obsClient, err := conf.ObjectStorageClient(OS_REGION_NAME) + if err != nil { + return fmt.Errorf("Error creating OBS client: %s", err) + } + + respList, err := obsClient.ListObjects(&obs.ListObjectsInput{ + Bucket: bucket, + ListObjsInput: obs.ListObjsInput{ + Prefix: key, + }, + }) + if err != nil { + return fmt.Errorf("Error listing objects of OBS bucket %s: %s", bucket, err) + } + + var exist bool + for _, content := range respList.Contents { + if key == content.Key { + exist = true + break + } + } + if !exist { + return fmt.Errorf("object %s not found in bucket %s", key, bucket) + } + + return nil + } +} + +func testAccObsBucketObjectDataSource_content(randInt int) (string, string) { + resource := fmt.Sprintf(` +resource "flexibleengine_obs_bucket" "object_bucket" { + bucket = "tf-acc-test-bucket-%d" +} + +resource "flexibleengine_obs_bucket_object" "object" { + bucket = flexibleengine_obs_bucket.object_bucket.bucket + key = "test-key-%d" + content = "some_bucket_content" +} +`, randInt, randInt) + + dataSource := fmt.Sprintf(` +%s + +data "flexibleengine_obs_bucket_object" "obj" { + bucket = "tf-acc-test-bucket-%d" + key = "test-key-%d" +}`, resource, randInt, randInt) + + return resource, dataSource +} + +func testAccObsBucketObjectDataSource_source(randInt int, source string) (string, string) { + resource := fmt.Sprintf(` +resource "flexibleengine_obs_bucket" "object_bucket" { + bucket = "tf-acc-test-bucket-%d" +} + +resource "flexibleengine_obs_bucket_object" "object" { + bucket = flexibleengine_obs_bucket.object_bucket.bucket + key = "test-key-%d" + source = "%s" + content_type = "binary/octet-stream" +} +`, randInt, randInt, source) + + dataSource := fmt.Sprintf(` +%s + +data "flexibleengine_obs_bucket_object" "obj" { + bucket = "tf-acc-test-bucket-%d" + key = "test-key-%d" +}`, resource, randInt, randInt) + + return resource, dataSource +} + +func testAccObsBucketObjectDataSource_allParams(randInt int) (string, string) { + resource := fmt.Sprintf(` +resource "flexibleengine_obs_bucket" "object_bucket" { + bucket = "tf-acc-test-bucket-%d" +} + +resource "flexibleengine_obs_bucket_object" "object" { + bucket = flexibleengine_obs_bucket.object_bucket.bucket + key = "test-key-%d" + acl = "private" + storage_class = "STANDARD" + encryption = true + content_type = "application/json" + content = <