diff --git a/docs/data-sources/s3_bucket_object.md b/docs/data-sources/s3_bucket_object.md deleted file mode 100644 index 9f68ebd7cb..0000000000 --- a/docs/data-sources/s3_bucket_object.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -subcategory: "Deprecated" ---- - -# huaweicloud\_s3\_bucket\_object - -!> **Warning:** It has been deprecated, use `huaweicloud obs` instead. - -The S3 object data source allows access to the metadata and -_optionally_ (see below) content of an object stored inside S3 bucket. - -~> **Note:** The content of an object (`body` field) is available only for objects which have a human-readable `Content-Type` (`text/*` and `application/json`). This is to prevent printing unsafe characters and potentially downloading large amount of data which would be thrown away in favour of metadata. - -## Example Usage - -```hcl -data "huaweicloud_s3_bucket_object" "b" { - bucket = "my-test-bucket" - key = "hello-world.zip" -} -``` - -## Argument Reference - -The following arguments are supported: - -* `bucket` - (Required, String) The name of the bucket to read the object from -* `key` - (Required, String) The full path to the object inside the bucket -* `version_id` - (Optional, String) Specific version ID of the object returned (defaults to latest version) - -## Attributes Reference - -In addition to all arguments above, the following attributes are exported: - -* `body` - Object data (see **limitations above** to understand cases in which this field is actually available) -* `cache_control` - Specifies caching behavior along the request/reply chain. -* `content_disposition` - Specifies presentational information for the object. -* `content_encoding` - Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied to obtain the media-type referenced by the Content-Type header field. -* `content_language` - The language the content is in. -* `content_length` - Size of the body in bytes. -* `content_type` - A standard MIME type describing the format of the object data. -* `etag` - [ETag](https://en.wikipedia.org/wiki/HTTP_ETag) generated for the object (an MD5 sum of the object content in case it's not encrypted) -* `expiration` - If the object expiration is configured (see [object lifecycle management](http://docs.huaweicloud.amazon.com/AmazonS3/latest/dev/object-lifecycle-mgmt.html)), the field includes this header. It includes the expiry-date and rule-id key value pairs providing object expiration information. The value of the rule-id is URL encoded. -* `expires` - The date and time at which the object is no longer cacheable. -* `last_modified` - Last modified date of the object in RFC1123 format (e.g. `Mon, 02 Jan 2006 15:04:05 MST`) -* `metadata` - A map of metadata stored with the object in S3 -* `server_side_encryption` - If the object is stored using server-side encryption (KMS or Amazon S3-managed encryption key), this field includes the chosen encryption and algorithm used. -* `sse_kms_key_id` - If present, specifies the ID of the Key Management Service (KMS) master encryption key that was used for the object. -* `storage_class` - [Storage class](http://docs.aws.amazon.com/AmazonS3/latest/dev/storage-class-intro.html) information of the object. Available for all objects except for `Standard` storage class objects. -* `version_id` - The latest version ID of the object returned. -* `website_redirect_location` - If the bucket is configured as a website, redirects requests for this object to another object in the same bucket or to an external URL. Amazon S3 stores the value of this header in the object metadata. -* `tags` - A mapping of tags assigned to the object. diff --git a/docs/resources/obs_bucket_policy.md b/docs/resources/obs_bucket_policy.md index f60534c81e..ac498c8c38 100644 --- a/docs/resources/obs_bucket_policy.md +++ b/docs/resources/obs_bucket_policy.md @@ -36,7 +36,7 @@ POLICY ### Policy with S3 format ```hcl -resource "huaweicloud_obs_bucket" "b" { +resource "huaweicloud_obs_bucket" "bucket" { bucket = "my-test-bucket" } @@ -68,7 +68,8 @@ POLICY The following arguments are supported: -* `region` - (Optional, String, ForceNew) The region in which to create the OBS bucket policy resource. If omitted, the provider-level region will be used. Changing this creates a new OBS bucket policy resource. +* `region` - (Optional, String, ForceNew) The region in which to create the OBS bucket policy resource. + If omitted, the provider-level region will be used. Changing this creates a new OBS bucket policy resource. * `bucket` - (Required, String, ForceNew) Specifies the name of the bucket to which to apply the policy. * `policy` - (Required, String) Specifies the text of the bucket policy in JSON format. For more information about diff --git a/docs/resources/s3_bucket.md b/docs/resources/s3_bucket.md deleted file mode 100644 index 56ac4a2c54..0000000000 --- a/docs/resources/s3_bucket.md +++ /dev/null @@ -1,240 +0,0 @@ ---- -subcategory: "Deprecated" ---- - -# huaweicloud\_s3\_bucket - -!> **Warning:** It has been deprecated, use `huaweicloud_obs_bucket` instead. - -Provides a S3 bucket resource. - -## Example Usage - -### Private Bucket with Tags - -```hcl -resource "huaweicloud_s3_bucket" "b" { - bucket = "my-tf-test-bucket" - acl = "private" - - tags = { - foo = "bar" - Env = "Test" - } -} -``` - -### Static Website Hosting - -```hcl -resource "huaweicloud_s3_bucket" "b" { - bucket = "s3-website-test.hashicorp.com" - acl = "public-read" - policy = file("policy.json") - - website { - index_document = "index.html" - error_document = "error.html" - - routing_rules = < **Warning:** It has been deprecated, use `huaweicloud_obs_bucket_object` instead. - -Provides a S3 bucket object resource. - -## Example Usage - -### Uploading a file to a bucket - -```hcl -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "your_bucket_name" - key = "new_object_key" - source = "path/to/file" - etag = md5(file("path/to/file")) -} - -resource "huaweicloud_s3_bucket" "examplebucket" { - bucket = "examplebuckettftest" - acl = "private" -} - -resource "huaweicloud_s3_bucket_object" "examplebucket_object" { - key = "someobject" - bucket = huaweicloud_s3_bucket.examplebucket.bucket - source = "index.html" -} -``` - -### Server Side Encryption with S3 Default Master Key - -```hcl -resource "huaweicloud_s3_bucket" "examplebucket" { - bucket = "examplebuckettftest" - acl = "private" -} - -resource "huaweicloud_s3_bucket_object" "examplebucket_object" { - key = "someobject" - bucket = huaweicloud_s3_bucket.examplebucket.bucket - source = "index.html" - server_side_encryption = "aws:kms" -} -``` - -## Argument Reference - --> **Note:** If you specify `content_encoding` you are responsible for encoding the body appropriately (i.e. `source` and `content` both expect already encoded/compressed bytes) - -The following arguments are supported: - -* `bucket` - (Required) The name of the bucket to put the file in. -* `key` - (Required) The name of the object once it is in the bucket. -* `source` - (Required) The path to the source file being uploaded to the bucket. -* `content` - (Required unless `source` given) The literal content being uploaded to the bucket. -* `acl` - (Optional) The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. Defaults to "private". -* `cache_control` - (Optional) Specifies caching behavior along the request/reply chain Read [w3c cache_control](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) for further details. -* `content_disposition` - (Optional) Specifies presentational information for the object. Read [wc3 content_disposition](http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1) for further information. -* `content_encoding` - (Optional) Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied to create the media-type referenced by the Content-Type header field. Read [w3c content encoding](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11) for further information. -* `content_language` - (Optional) The language the content is in e.g. en-US or en-GB. -* `content_type` - (Optional) 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. -* `website_redirect` - (Optional) Specifies a target URL for [website redirect](http://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-page-redirect.html). -* `etag` - (Optional) Used to trigger updates. The only meaningful value is `${md5(file("path/to/file"))}`. -This attribute is not compatible with `kms_key_id`. -* `server_side_encryption` - (Optional) Specifies server-side encryption of the object in S3. Valid values are "`AES256`" and "`aws:kms`". -* `sse_kms_key_id` - (Optional) The ID of the kms key. - -Either `source` or `content` must be provided to specify the bucket content. -These two arguments are mutually-exclusive. - -## Attributes 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). -* `version_id` - A unique version ID value for the object, if bucket versioning -is enabled. diff --git a/docs/resources/s3_bucket_policy.md b/docs/resources/s3_bucket_policy.md deleted file mode 100644 index ceab756ed4..0000000000 --- a/docs/resources/s3_bucket_policy.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -subcategory: "Deprecated" ---- - -# huaweicloud\_s3\_bucket\_policy - -!> **Warning:** It has been deprecated, use `huaweicloud_obs_bucket_policy` instead. - -Attaches a policy to an S3 bucket resource. - -## Example Usage - -### Basic Usage - -```hcl -resource "huaweicloud_s3_bucket" "b" { - bucket = "my-tf-test-bucket" -} - -resource "huaweicloud_s3_bucket_policy" "policy" { - bucket = huaweicloud_s3_bucket.b.id - policy = < 0 { - client.Timeout = newTimeout - } else { - log.Printf("[WARN] Non-positive value of %s (%s) is meaningless, ignoring", userTimeoutEnvVar, newTimeout.String()) - } - } else { - log.Printf("[WARN] Error converting %s to time.Duration: %s", userTimeoutEnvVar, err) - } - } - - log.Printf("[INFO] Setting AWS metadata API timeout to %s", client.Timeout.String()) - cfg := &aws.Config{ - HTTPClient: client, - } - usedEndpoint := setOptionalEndpoint(cfg) - - // Add the default AWS provider for ECS Task Roles if the relevant env variable is set - if uri := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); len(uri) > 0 { - providers = append(providers, defaults.RemoteCredProvider(*cfg, defaults.Handlers())) - log.Print("[INFO] ECS container credentials detected, RemoteCredProvider added to auth chain") - } - - // Real AWS should reply to a simple metadata request. - // We check it actually does to ensure something else didn't just - // happen to be listening on the same IP:Port - metadataClient := ec2metadata.New(session.New(cfg)) - if metadataClient.Available() { - providers = append(providers, &ec2rolecreds.EC2RoleProvider{ - Client: metadataClient, - }) - log.Print("[INFO] AWS EC2 instance detected via default metadata" + - " API endpoint, EC2RoleProvider added to the auth chain") - } else { - if usedEndpoint == "" { - usedEndpoint = "default location" - } - log.Printf("[INFO] Ignoring AWS metadata API endpoint at %s "+ - "as it doesn't return any instance-id", usedEndpoint) - } - - return awsCredentials.NewChainCredentials(providers), nil -} - -func setOptionalEndpoint(cfg *aws.Config) string { - endpoint := os.Getenv("AWS_METADATA_URL") - if endpoint != "" { - log.Printf("[INFO] Setting custom metadata endpoint: %q", endpoint) - cfg.Endpoint = aws.String(endpoint) - return endpoint - } - return "" -} diff --git a/huaweicloud/awserr.go b/huaweicloud/awserr.go deleted file mode 100644 index 6b31684171..0000000000 --- a/huaweicloud/awserr.go +++ /dev/null @@ -1,46 +0,0 @@ -package huaweicloud - -import ( - "time" - - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" -) - -func retryOnAwsCode(code string, f func() (interface{}, error)) (interface{}, error) { - var resp interface{} - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - var err error - resp, err = f() - if err != nil { - awsErr, ok := err.(awserr.Error) - if ok && awsErr.Code() == code { - return resource.RetryableError(err) - } - return resource.NonRetryableError(err) - } - return nil - }) - return resp, err -} - -func retryOnAwsCodes(codes []string, f func() (interface{}, error)) (interface{}, error) { - var resp interface{} - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - var err error - resp, err = f() - if err != nil { - awsErr, ok := err.(awserr.Error) - if ok { - for _, code := range codes { - if awsErr.Code() == code { - return resource.RetryableError(err) - } - } - } - return resource.NonRetryableError(err) - } - return nil - }) - return resp, err -} diff --git a/huaweicloud/config.go b/huaweicloud/config.go index 6cd0772b2c..62753bd165 100644 --- a/huaweicloud/config.go +++ b/huaweicloud/config.go @@ -6,15 +6,8 @@ import ( "fmt" "log" "net/http" - "strings" "sync" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/hashicorp/errwrap" - cleanhttp "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/terraform-plugin-sdk/helper/logging" "github.com/hashicorp/terraform-plugin-sdk/helper/pathorcontents" "github.com/huaweicloud/golangsdk" @@ -55,9 +48,7 @@ type Config struct { RegionClient bool EnterpriseProjectID string - HwClient *golangsdk.ProviderClient - s3sess *session.Session - + HwClient *golangsdk.ProviderClient DomainClient *golangsdk.ProviderClient // the custom endpoints used to override the default endpoint URL @@ -106,7 +97,7 @@ func (c *Config) LoadAndValidate() error { } } - return c.newS3Session(logging.IsDebugOrHigher()) + return nil } func generateTLSConfig(c *Config) (*tls.Config, error) { @@ -189,56 +180,6 @@ func genClient(c *Config, ao golangsdk.AuthOptionsProvider) (*golangsdk.Provider return client, nil } -func (c *Config) newS3Session(osDebug bool) error { - - if c.AccessKey != "" && c.SecretKey != "" { - // Setup S3 client/config information for Swift S3 buckets - log.Println("[INFO] Building Swift S3 auth structure") - creds, err := GetCredentials(c) - if err != nil { - return err - } - // Call Get to check for credential provider. If nothing found, we'll get an - // error, and we can present it nicely to the user - cp, err := creds.Get() - if err != nil { - if sErr, ok := err.(awserr.Error); ok && sErr.Code() == "NoCredentialProviders" { - return fmt.Errorf("No valid credential sources found for S3 Provider.") - } - - return fmt.Errorf("Error loading credentials for S3 Provider: %s", err) - } - - log.Printf("[INFO] S3 Auth provider used: %q", cp.ProviderName) - - sConfig := &aws.Config{ - Credentials: creds, - Region: aws.String(c.Region), - HTTPClient: cleanhttp.DefaultClient(), - } - - if osDebug { - sConfig.LogLevel = aws.LogLevel(aws.LogDebugWithHTTPBody | aws.LogDebugWithRequestRetries | aws.LogDebugWithRequestErrors) - sConfig.Logger = sLogger{} - } - - if c.Insecure { - transport := sConfig.HTTPClient.Transport.(*http.Transport) - transport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - } - - // Set up base session for S3 - c.s3sess, err = session.NewSession(sConfig) - if err != nil { - return errwrap.Wrapf("Error creating Swift S3 session: {{err}}", err) - } - } - - return nil -} - func buildClientByToken(c *Config) error { var pao, dao golangsdk.AuthOptions @@ -370,18 +311,6 @@ func genClients(c *Config, pao, dao golangsdk.AuthOptionsProvider) error { return err } -type sLogger struct{} - -func (l sLogger) Log(args ...interface{}) { - tokens := make([]string, 0, len(args)) - for _, arg := range args { - if token, ok := arg.(string); ok { - tokens = append(tokens, token) - } - } - log.Printf("[DEBUG] [aws-sdk-go] %s", strings.Join(tokens, " ")) -} - func getObsEndpoint(c *Config, region string) string { if endpoint, ok := c.Endpoints["obs"]; ok { return endpoint @@ -389,18 +318,6 @@ func getObsEndpoint(c *Config, region string) string { return fmt.Sprintf("https://obs.%s.%s/", region, c.Cloud) } -func (c *Config) computeS3conn(region string) (*s3.S3, error) { - if c.s3sess == nil { - return nil, fmt.Errorf("missing credentials for Swift S3 Provider, need access_key and secret_key values for provider") - } - - obsEndpoint := getObsEndpoint(c, region) - S3Sess := c.s3sess.Copy(&aws.Config{Endpoint: aws.String(obsEndpoint)}) - s3conn := s3.New(S3Sess) - - return s3conn, nil -} - func (c *Config) NewObjectStorageClientWithSignature(region string) (*obs.ObsClient, error) { if c.AccessKey == "" || c.SecretKey == "" { return nil, fmt.Errorf("missing credentials for OBS, need access_key and secret_key values for provider") diff --git a/huaweicloud/data_source_huaweicloud_obs_bucket_object_test.go b/huaweicloud/data_source_huaweicloud_obs_bucket_object_test.go index 7a5a477503..acf402179f 100644 --- a/huaweicloud/data_source_huaweicloud_obs_bucket_object_test.go +++ b/huaweicloud/data_source_huaweicloud_obs_bucket_object_test.go @@ -17,7 +17,7 @@ func TestAccHuaweiCloudObsBucketObjectDataSource_content(t *testing.T) { resourceConf, dataSourceConf := testAccHuaweiCloudObsBucketObjectDataSource_content(rInt) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckS3(t) }, + PreCheck: func() { testAccPreCheckOBS(t) }, Providers: testAccProviders, PreventPostDestroyRefresh: true, Steps: []resource.TestStep{ @@ -60,7 +60,7 @@ func TestAccHuaweiCloudObsBucketObjectDataSource_source(t *testing.T) { resourceConf, dataSourceConf := testAccHuaweiCloudObsBucketObjectDataSource_source(rInt, tmpFile.Name()) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckS3(t) }, + PreCheck: func() { testAccPreCheckOBS(t) }, Providers: testAccProviders, PreventPostDestroyRefresh: true, Steps: []resource.TestStep{ @@ -87,7 +87,7 @@ func TestAccHuaweiCloudObsBucketObjectDataSource_allParams(t *testing.T) { resourceConf, dataSourceConf := testAccHuaweiCloudObsBucketObjectDataSource_allParams(rInt) resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckS3(t) }, + PreCheck: func() { testAccPreCheckOBS(t) }, Providers: testAccProviders, PreventPostDestroyRefresh: true, Steps: []resource.TestStep{ diff --git a/huaweicloud/data_source_huaweicloud_s3_bucket_object.go b/huaweicloud/data_source_huaweicloud_s3_bucket_object.go deleted file mode 100644 index c1d2f81b74..0000000000 --- a/huaweicloud/data_source_huaweicloud_s3_bucket_object.go +++ /dev/null @@ -1,224 +0,0 @@ -package huaweicloud - -import ( - "bytes" - "fmt" - "log" - "regexp" - "strings" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" -) - -func dataSourceS3BucketObject() *schema.Resource { - return &schema.Resource{ - Read: dataSourceS3BucketObjectRead, - DeprecationMessage: "use huaweicloud obs resource instead", - - Schema: map[string]*schema.Schema{ - "body": { - Type: schema.TypeString, - Computed: true, - }, - "bucket": { - Type: schema.TypeString, - Required: true, - }, - "cache_control": { - Type: schema.TypeString, - Computed: true, - }, - "content_disposition": { - Type: schema.TypeString, - Computed: true, - }, - "content_encoding": { - Type: schema.TypeString, - Computed: true, - }, - "content_language": { - Type: schema.TypeString, - Computed: true, - }, - "content_length": { - Type: schema.TypeInt, - Computed: true, - }, - "content_type": { - Type: schema.TypeString, - Computed: true, - }, - "etag": { - Type: schema.TypeString, - Computed: true, - }, - "expiration": { - Type: schema.TypeString, - Computed: true, - }, - "expires": { - Type: schema.TypeString, - Computed: true, - }, - "key": { - Type: schema.TypeString, - Required: true, - }, - "last_modified": { - Type: schema.TypeString, - Computed: true, - }, - "metadata": { - Type: schema.TypeMap, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "range": { - Type: schema.TypeString, - Optional: true, - }, - "server_side_encryption": { - Type: schema.TypeString, - Computed: true, - }, - "sse_kms_key_id": { - Type: schema.TypeString, - Computed: true, - }, - "version_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "website_redirect_location": { - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func dataSourceS3BucketObjectRead(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - conn, err := config.computeS3conn(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - bucket := d.Get("bucket").(string) - key := d.Get("key").(string) - - input := s3.HeadObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - } - if v, ok := d.GetOk("range"); ok { - input.Range = aws.String(v.(string)) - } - if v, ok := d.GetOk("version_id"); ok { - input.VersionId = aws.String(v.(string)) - } - - versionText := "" - uniqueId := bucket + "/" + key - if v, ok := d.GetOk("version_id"); ok { - versionText = fmt.Sprintf(" of version %q", v.(string)) - uniqueId += "@" + v.(string) - } - - log.Printf("[DEBUG] Reading S3 object: %s", input) - out, err := conn.HeadObject(&input) - if err != nil { - return fmt.Errorf("Failed getting S3 object: %s Bucket: %q Object: %q", err, bucket, key) - } - if out.DeleteMarker != nil && *out.DeleteMarker == true { - return fmt.Errorf("Requested S3 object %q%s has been deleted", - bucket+key, versionText) - } - - log.Printf("[DEBUG] Received S3 object: %s", out) - - d.SetId(uniqueId) - - d.Set("cache_control", out.CacheControl) - d.Set("content_disposition", out.ContentDisposition) - d.Set("content_encoding", out.ContentEncoding) - d.Set("content_language", out.ContentLanguage) - d.Set("content_length", out.ContentLength) - d.Set("content_type", out.ContentType) - // See https://forums.aws.amazon.com/thread.jspa?threadID=44003 - d.Set("etag", strings.Trim(*out.ETag, `"`)) - d.Set("expiration", out.Expiration) - d.Set("expires", out.Expires) - d.Set("last_modified", out.LastModified.Format(time.RFC1123)) - if err := d.Set("metadata", pointersMapToStringList(out.Metadata)); err != nil { - return fmt.Errorf("[DEBUG] Error saving metadata to state for HuaweiCloud S3 object (%s): %s", d.Id(), err) - } - d.Set("server_side_encryption", out.ServerSideEncryption) - d.Set("sse_kms_key_id", out.SSEKMSKeyId) - d.Set("version_id", out.VersionId) - d.Set("website_redirect_location", out.WebsiteRedirectLocation) - - if isContentTypeAllowed(out.ContentType) { - input := s3.GetObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - } - if v, ok := d.GetOk("range"); ok { - input.Range = aws.String(v.(string)) - } - if out.VersionId != nil { - input.VersionId = out.VersionId - } - out, err := conn.GetObject(&input) - if err != nil { - return fmt.Errorf("Failed getting S3 object: %s", err) - } - - buf := new(bytes.Buffer) - bytesRead, err := buf.ReadFrom(out.Body) - if err != nil { - return fmt.Errorf("Failed reading content of S3 object (%s): %s", - uniqueId, err) - } - log.Printf("[INFO] Saving %d bytes from S3 object %s", bytesRead, uniqueId) - d.Set("body", buf.String()) - } else { - contentType := "" - if out.ContentType == nil { - contentType = "" - } else { - contentType = *out.ContentType - } - - log.Printf("[INFO] Ignoring body of S3 object %s with Content-Type %q", - uniqueId, contentType) - } - - return nil -} - -// This is to prevent potential issues w/ binary files -// and generally unprintable characters -// See https://github.com/hashicorp/terraform/pull/3858#issuecomment-156856738 -func isContentTypeAllowed(contentType *string) bool { - if contentType == nil { - return false - } - - allowedContentTypes := []*regexp.Regexp{ - regexp.MustCompile("^text/.+"), - regexp.MustCompile("^application/json$"), - } - - for _, r := range allowedContentTypes { - if r.MatchString(*contentType) { - return true - } - } - - return false -} diff --git a/huaweicloud/data_source_huaweicloud_s3_bucket_object_test.go b/huaweicloud/data_source_huaweicloud_s3_bucket_object_test.go deleted file mode 100644 index 8e55ed5ee4..0000000000 --- a/huaweicloud/data_source_huaweicloud_s3_bucket_object_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package huaweicloud - -import ( - "fmt" - "regexp" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" -) - -func TestAccDataSourceS3BucketObject_basic(t *testing.T) { - rInt := acctest.RandInt() - resourceOnlyConf, conf := testAccDataSourceS3ObjectConfig_basic(rInt) - - var rObj s3.GetObjectOutput - var dsObj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - PreventPostDestroyRefresh: true, - Steps: []resource.TestStep{ - { - Config: resourceOnlyConf, - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &rObj), - ), - }, - { - Config: conf, - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsS3ObjectDataSourceExists("data.huaweicloud_s3_bucket_object.obj", &dsObj), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_length", "11"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_type", "binary/octet-stream"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "etag", "b10a8db164e0754105b7a99be72e3fe5"), - resource.TestMatchResourceAttr("data.huaweicloud_s3_bucket_object.obj", "last_modified", - regexp.MustCompile("^[a-zA-Z]{3}, [0-9]+ [a-zA-Z]+ [0-9]{4} [0-9:]+ [A-Z]+$")), - resource.TestCheckNoResourceAttr("data.huaweicloud_s3_bucket_object.obj", "body"), - ), - }, - }, - }) -} - -func TestAccDataSourceS3BucketObject_readableBody(t *testing.T) { - rInt := acctest.RandInt() - resourceOnlyConf, conf := testAccDataSourceS3ObjectConfig_readableBody(rInt) - - var rObj s3.GetObjectOutput - var dsObj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - PreventPostDestroyRefresh: true, - Steps: []resource.TestStep{ - { - Config: resourceOnlyConf, - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &rObj), - ), - }, - { - Config: conf, - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsS3ObjectDataSourceExists("data.huaweicloud_s3_bucket_object.obj", &dsObj), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_length", "3"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_type", "text/plain"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "etag", "a6105c0a611b41b08f1209506350279e"), - resource.TestMatchResourceAttr("data.huaweicloud_s3_bucket_object.obj", "last_modified", - regexp.MustCompile("^[a-zA-Z]{3}, [0-9]+ [a-zA-Z]+ [0-9]{4} [0-9:]+ [A-Z]+$")), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "body", "yes"), - ), - }, - }, - }) -} - -func TestAccDataSourceAWSS3BucketObject_allParams(t *testing.T) { - rInt := acctest.RandInt() - resourceOnlyConf, conf := testAccDataSourceS3ObjectConfig_allParams(rInt) - - var rObj s3.GetObjectOutput - var dsObj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - PreventPostDestroyRefresh: true, - Steps: []resource.TestStep{ - { - Config: resourceOnlyConf, - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &rObj), - ), - }, - { - Config: conf, - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsS3ObjectDataSourceExists("data.huaweicloud_s3_bucket_object.obj", &dsObj), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_length", "21"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_type", "application/unknown"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "etag", "723f7a6ac0c57b445790914668f98640"), - resource.TestMatchResourceAttr("data.huaweicloud_s3_bucket_object.obj", "last_modified", - regexp.MustCompile("^[a-zA-Z]{3}, [0-9]+ [a-zA-Z]+ [0-9]{4} [0-9:]+ [A-Z]+$")), - resource.TestCheckNoResourceAttr("data.huaweicloud_s3_bucket_object.obj", "body"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "cache_control", "no-cache"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_disposition", "attachment"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_encoding", "identity"), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "content_language", "en-GB"), - // Encryption is off - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "server_side_encryption", ""), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "sse_kms_key_id", ""), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "expiration", ""), - // Currently unsupported in huaweicloud_s3_bucket_object resource - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "expires", ""), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "website_redirect_location", ""), - resource.TestCheckResourceAttr("data.huaweicloud_s3_bucket_object.obj", "metadata.%", "0"), - ), - }, - }, - }) -} - -func testAccCheckAwsS3ObjectDataSourceExists(n string, obj *s3.GetObjectOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Can't find S3 object data source: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("S3 object data source ID not set") - } - - config := testAccProvider.Meta().(*Config) - s3conn, err := config.computeS3conn(HW_REGION_NAME) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - out, err := s3conn.GetObject( - &s3.GetObjectInput{ - Bucket: aws.String(rs.Primary.Attributes["bucket"]), - Key: aws.String(rs.Primary.Attributes["key"]), - }) - if err != nil { - return fmt.Errorf("Failed getting S3 Object from %s: %s", - rs.Primary.Attributes["bucket"]+"/"+rs.Primary.Attributes["key"], err) - } - - *obj = *out - - return nil - } -} - -func testAccDataSourceS3ObjectConfig_basic(randInt int) (string, string) { - resources := fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket" { - bucket = "tf-object-test-bucket-%d" -} -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket.bucket}" - key = "tf-testing-obj-%d" - content = "Hello World" -} -`, randInt, randInt) - - both := fmt.Sprintf(`%s -data "huaweicloud_s3_bucket_object" "obj" { - bucket = "tf-object-test-bucket-%d" - key = "tf-testing-obj-%d" -}`, resources, randInt, randInt) - - return resources, both -} - -func testAccDataSourceS3ObjectConfig_readableBody(randInt int) (string, string) { - resources := fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket" { - bucket = "tf-object-test-bucket-%d" -} -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket.bucket}" - key = "tf-testing-obj-%d-readable" - content = "yes" - content_type = "text/plain" -} -`, randInt, randInt) - - both := fmt.Sprintf(`%s -data "huaweicloud_s3_bucket_object" "obj" { - bucket = "tf-object-test-bucket-%d" - key = "tf-testing-obj-%d-readable" -}`, resources, randInt, randInt) - - return resources, both -} - -func testAccDataSourceS3ObjectConfig_allParams(randInt int) (string, string) { - resources := fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket" { - bucket = "tf-object-test-bucket-%d" - versioning { - enabled = true - } -} - -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket.bucket}" - key = "tf-testing-obj-%d-all-params" - content = < 0 { - rules := make([]map[string]interface{}, 0, len(lifecycle.Rules)) - - for _, lifecycleRule := range lifecycle.Rules { - rule := make(map[string]interface{}) - - // ID - if lifecycleRule.ID != nil && *lifecycleRule.ID != "" { - rule["id"] = *lifecycleRule.ID - } - filter := lifecycleRule.Filter - if filter != nil { - if filter.And != nil { - // Prefix - if filter.And.Prefix != nil && *filter.And.Prefix != "" { - rule["prefix"] = *filter.And.Prefix - } - } else { - // Prefix - if filter.Prefix != nil && *filter.Prefix != "" { - rule["prefix"] = *filter.Prefix - } - } - } else { - if lifecycleRule.Prefix != nil { - rule["prefix"] = *lifecycleRule.Prefix - } - } - - // Enabled - if lifecycleRule.Status != nil { - if *lifecycleRule.Status == s3.ExpirationStatusEnabled { - rule["enabled"] = true - } else { - rule["enabled"] = false - } - } - - // AbortIncompleteMultipartUploadDays - if lifecycleRule.AbortIncompleteMultipartUpload != nil { - if lifecycleRule.AbortIncompleteMultipartUpload.DaysAfterInitiation != nil { - rule["abort_incomplete_multipart_upload_days"] = int(*lifecycleRule.AbortIncompleteMultipartUpload.DaysAfterInitiation) - } - } - - // expiration - if lifecycleRule.Expiration != nil { - e := make(map[string]interface{}) - if lifecycleRule.Expiration.Date != nil { - e["date"] = (*lifecycleRule.Expiration.Date).Format("2006-01-02") - } - if lifecycleRule.Expiration.Days != nil { - e["days"] = int(*lifecycleRule.Expiration.Days) - } - if lifecycleRule.Expiration.ExpiredObjectDeleteMarker != nil { - e["expired_object_delete_marker"] = *lifecycleRule.Expiration.ExpiredObjectDeleteMarker - } - rule["expiration"] = schema.NewSet(expirationHash, []interface{}{e}) - } - // noncurrent_version_expiration - if lifecycleRule.NoncurrentVersionExpiration != nil { - e := make(map[string]interface{}) - if lifecycleRule.NoncurrentVersionExpiration.NoncurrentDays != nil { - e["days"] = int(*lifecycleRule.NoncurrentVersionExpiration.NoncurrentDays) - } - rule["noncurrent_version_expiration"] = schema.NewSet(expirationHash, []interface{}{e}) - } - //// transition - if len(lifecycleRule.Transitions) > 0 { - transitions := make([]interface{}, 0, len(lifecycleRule.Transitions)) - for _, v := range lifecycleRule.Transitions { - t := make(map[string]interface{}) - if v.Date != nil { - t["date"] = (*v.Date).Format("2006-01-02") - } - if v.Days != nil { - t["days"] = int(*v.Days) - } - if v.StorageClass != nil { - t["storage_class"] = *v.StorageClass - } - transitions = append(transitions, t) - } - rule["transition"] = schema.NewSet(transitionHash, transitions) - } - // noncurrent_version_transition - if len(lifecycleRule.NoncurrentVersionTransitions) > 0 { - transitions := make([]interface{}, 0, len(lifecycleRule.NoncurrentVersionTransitions)) - for _, v := range lifecycleRule.NoncurrentVersionTransitions { - t := make(map[string]interface{}) - if v.NoncurrentDays != nil { - t["days"] = int(*v.NoncurrentDays) - } - if v.StorageClass != nil { - t["storage_class"] = *v.StorageClass - } - transitions = append(transitions, t) - } - rule["noncurrent_version_transition"] = schema.NewSet(transitionHash, transitions) - } - - rules = append(rules, rule) - } - - if err := d.Set("lifecycle_rule", rules); err != nil { - return err - } - } - - // Add the region as an attribute - - locationResponse, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.GetBucketLocation( - &s3.GetBucketLocationInput{ - Bucket: aws.String(d.Id()), - }, - ) - }) - location := locationResponse.(*s3.GetBucketLocationOutput) - if err != nil { - return err - } - var region string - if location.LocationConstraint != nil { - region = *location.LocationConstraint - } - region = normalizeRegion(region) - if err := d.Set("region", region); err != nil { - return err - } - - // Add the hosted zone ID for this bucket's region as an attribute - // UNUSED? - /* - hostedZoneID := HostedZoneIDForRegion(region) - if err := d.Set("hosted_zone_id", hostedZoneID); err != nil { - return err - } - */ - - // Add website_endpoint as an attribute - websiteEndpoint, err := websiteEndpoint(s3conn, d) - if err != nil { - return err - } - if websiteEndpoint != nil { - if err := d.Set("website_endpoint", websiteEndpoint.Endpoint); err != nil { - return err - } - if err := d.Set("website_domain", websiteEndpoint.Domain); err != nil { - return err - } - } - - // get tags - tagSet, err := getTagSetS3(s3conn, d.Id()) - if err != nil { - return err - } - - if err := d.Set("tags", tagsToMapS3(tagSet)); err != nil { - return err - } - - // UNUSED? - //d.Set("arn", fmt.Sprintf("arn:%s:s3:::%s", meta.(*Config).partition, d.Id())) - - return nil -} - -func resourceS3BucketDelete(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - s3conn, err := config.computeS3conn(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - log.Printf("[DEBUG] S3 Delete Bucket: %s", d.Id()) - _, err = s3conn.DeleteBucket(&s3.DeleteBucketInput{ - Bucket: aws.String(d.Id()), - }) - if err != nil { - ec2err, ok := err.(awserr.Error) - if ok && ec2err.Code() == "BucketNotEmpty" { - if d.Get("force_destroy").(bool) { - // bucket may have things delete them - log.Printf("[DEBUG] S3 Bucket attempting to forceDestroy %+v", err) - - bucket := d.Get("bucket").(string) - resp, err := s3conn.ListObjectVersions( - &s3.ListObjectVersionsInput{ - Bucket: aws.String(bucket), - }, - ) - - if err != nil { - return fmt.Errorf("Error S3 Bucket list Object Versions err: %s", err) - } - - objectsToDelete := make([]*s3.ObjectIdentifier, 0) - - if len(resp.DeleteMarkers) != 0 { - - for _, v := range resp.DeleteMarkers { - objectsToDelete = append(objectsToDelete, &s3.ObjectIdentifier{ - Key: v.Key, - VersionId: v.VersionId, - }) - } - } - - if len(resp.Versions) != 0 { - for _, v := range resp.Versions { - objectsToDelete = append(objectsToDelete, &s3.ObjectIdentifier{ - Key: v.Key, - VersionId: v.VersionId, - }) - } - } - - params := &s3.DeleteObjectsInput{ - Bucket: aws.String(bucket), - Delete: &s3.Delete{ - Objects: objectsToDelete, - }, - } - - _, err = s3conn.DeleteObjects(params) - - if err != nil { - return fmt.Errorf("Error S3 Bucket force_destroy error deleting: %s", err) - } - - // this line recurses until all objects are deleted or an error is returned - return resourceS3BucketDelete(d, meta) - } - } - return fmt.Errorf("Error deleting S3 Bucket: %s %q", err, d.Get("bucket").(string)) - } - return nil -} - -func resourceS3BucketPolicyUpdate(s3conn *s3.S3, d *schema.ResourceData) error { - bucket := d.Get("bucket").(string) - policy := d.Get("policy").(string) - - if policy != "" { - log.Printf("[DEBUG] S3 bucket: %s, put policy: %s", bucket, policy) - - params := &s3.PutBucketPolicyInput{ - Bucket: aws.String(bucket), - Policy: aws.String(policy), - } - - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - if _, err := s3conn.PutBucketPolicy(params); err != nil { - if awserr, ok := err.(awserr.Error); ok { - if awserr.Code() == "MalformedPolicy" || awserr.Code() == "NoSuchBucket" { - return resource.RetryableError(awserr) - } - } - return resource.NonRetryableError(err) - } - return nil - }) - - if err != nil { - return fmt.Errorf("Error putting S3 policy: %s", err) - } - } else { - log.Printf("[DEBUG] S3 bucket: %s, delete policy: %s", bucket, policy) - _, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{ - Bucket: aws.String(bucket), - }) - }) - - if err != nil { - return fmt.Errorf("Error deleting S3 policy: %s", err) - } - } - - return nil -} - -func resourceS3BucketCorsUpdate(s3conn *s3.S3, d *schema.ResourceData) error { - bucket := d.Get("bucket").(string) - rawCors := d.Get("cors_rule").([]interface{}) - - if len(rawCors) == 0 { - // Delete CORS - log.Printf("[DEBUG] S3 bucket: %s, delete CORS", bucket) - - _, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.DeleteBucketCors(&s3.DeleteBucketCorsInput{ - Bucket: aws.String(bucket), - }) - }) - if err != nil { - return fmt.Errorf("Error deleting S3 CORS: %s", err) - } - } else { - // Put CORS - rules := make([]*s3.CORSRule, 0, len(rawCors)) - for _, cors := range rawCors { - corsMap := cors.(map[string]interface{}) - r := &s3.CORSRule{} - for k, v := range corsMap { - log.Printf("[DEBUG] S3 bucket: %s, put CORS: %#v, %#v", bucket, k, v) - if k == "max_age_seconds" { - r.MaxAgeSeconds = aws.Int64(int64(v.(int))) - } else { - vMap := make([]*string, len(v.([]interface{}))) - for i, vv := range v.([]interface{}) { - str := vv.(string) - vMap[i] = aws.String(str) - } - switch k { - case "allowed_headers": - r.AllowedHeaders = vMap - case "allowed_methods": - r.AllowedMethods = vMap - case "allowed_origins": - r.AllowedOrigins = vMap - case "expose_headers": - r.ExposeHeaders = vMap - } - } - } - rules = append(rules, r) - } - corsInput := &s3.PutBucketCorsInput{ - Bucket: aws.String(bucket), - CORSConfiguration: &s3.CORSConfiguration{ - CORSRules: rules, - }, - } - log.Printf("[DEBUG] S3 bucket: %s, put CORS: %#v", bucket, corsInput) - - _, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.PutBucketCors(corsInput) - }) - if err != nil { - return fmt.Errorf("Error putting S3 CORS: %s", err) - } - } - - return nil -} - -func resourceS3BucketWebsiteUpdate(s3conn *s3.S3, d *schema.ResourceData) error { - ws := d.Get("website").([]interface{}) - - if len(ws) == 1 { - var w map[string]interface{} - if ws[0] != nil { - w = ws[0].(map[string]interface{}) - } else { - w = make(map[string]interface{}) - } - return resourceS3BucketWebsitePut(s3conn, d, w) - } else if len(ws) == 0 { - return resourceS3BucketWebsiteDelete(s3conn, d) - } else { - return fmt.Errorf("Cannot specify more than one website.") - } -} - -func resourceS3BucketWebsitePut(s3conn *s3.S3, d *schema.ResourceData, website map[string]interface{}) error { - bucket := d.Get("bucket").(string) - - var indexDocument, errorDocument, redirectAllRequestsTo, routingRules string - if v, ok := website["index_document"]; ok { - indexDocument = v.(string) - } - if v, ok := website["error_document"]; ok { - errorDocument = v.(string) - } - if v, ok := website["redirect_all_requests_to"]; ok { - redirectAllRequestsTo = v.(string) - } - if v, ok := website["routing_rules"]; ok { - routingRules = v.(string) - } - - if indexDocument == "" && redirectAllRequestsTo == "" { - return fmt.Errorf("Must specify either index_document or redirect_all_requests_to.") - } - - websiteConfiguration := &s3.WebsiteConfiguration{} - - if indexDocument != "" { - websiteConfiguration.IndexDocument = &s3.IndexDocument{Suffix: aws.String(indexDocument)} - } - - if errorDocument != "" { - websiteConfiguration.ErrorDocument = &s3.ErrorDocument{Key: aws.String(errorDocument)} - } - - if redirectAllRequestsTo != "" { - redirect, err := url.Parse(redirectAllRequestsTo) - if err == nil && redirect.Scheme != "" { - var redirectHostBuf bytes.Buffer - redirectHostBuf.WriteString(redirect.Host) - if redirect.Path != "" { - redirectHostBuf.WriteString(redirect.Path) - } - websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectHostBuf.String()), Protocol: aws.String(redirect.Scheme)} - } else { - websiteConfiguration.RedirectAllRequestsTo = &s3.RedirectAllRequestsTo{HostName: aws.String(redirectAllRequestsTo)} - } - } - - if routingRules != "" { - var unmarshaledRules []*s3.RoutingRule - if err := json.Unmarshal([]byte(routingRules), &unmarshaledRules); err != nil { - return err - } - websiteConfiguration.RoutingRules = unmarshaledRules - } - - putInput := &s3.PutBucketWebsiteInput{ - Bucket: aws.String(bucket), - WebsiteConfiguration: websiteConfiguration, - } - - log.Printf("[DEBUG] S3 put bucket website: %#v", putInput) - - _, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.PutBucketWebsite(putInput) - }) - if err != nil { - return fmt.Errorf("Error putting S3 website: %s", err) - } - - return nil -} - -func resourceS3BucketWebsiteDelete(s3conn *s3.S3, d *schema.ResourceData) error { - bucket := d.Get("bucket").(string) - deleteInput := &s3.DeleteBucketWebsiteInput{Bucket: aws.String(bucket)} - - log.Printf("[DEBUG] S3 delete bucket website: %#v", deleteInput) - - _, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.DeleteBucketWebsite(deleteInput) - }) - if err != nil { - return fmt.Errorf("Error deleting S3 website: %s", err) - } - - d.Set("website_endpoint", "") - d.Set("website_domain", "") - - return nil -} - -func websiteEndpoint(s3conn *s3.S3, d *schema.ResourceData) (*S3Website, error) { - // If the bucket doesn't have a website configuration, return an empty - // endpoint - if _, ok := d.GetOk("website"); !ok { - return nil, nil - } - - bucket := d.Get("bucket").(string) - - // Lookup the region for this bucket - - locationResponse, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.GetBucketLocation( - &s3.GetBucketLocationInput{ - Bucket: aws.String(bucket), - }, - ) - }) - location := locationResponse.(*s3.GetBucketLocationOutput) - if err != nil { - return nil, err - } - var region string - if location.LocationConstraint != nil { - region = *location.LocationConstraint - } - - return WebsiteEndpoint(bucket, region), nil -} - -func bucketDomainName(bucket, region string) string { - return fmt.Sprintf("%s.obs.%s.myhuaweicloud.com", bucket, region) -} - -func WebsiteEndpoint(bucket string, region string) *S3Website { - domain := WebsiteDomainUrl(region) - return &S3Website{Endpoint: fmt.Sprintf("%s.%s", bucket, domain), Domain: domain} -} - -func WebsiteDomainUrl(region string) string { - region = normalizeRegion(region) - - return fmt.Sprintf("s3-website.%s.amazonaws.com", region) -} - -func resourceS3BucketAclUpdate(s3conn *s3.S3, d *schema.ResourceData) error { - acl := d.Get("acl").(string) - bucket := d.Get("bucket").(string) - - i := &s3.PutBucketAclInput{ - Bucket: aws.String(bucket), - ACL: aws.String(acl), - } - log.Printf("[DEBUG] S3 put bucket ACL: %#v", i) - - _, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.PutBucketAcl(i) - }) - if err != nil { - return fmt.Errorf("Error putting S3 ACL: %s", err) - } - - return nil -} - -func resourceS3BucketVersioningUpdate(s3conn *s3.S3, d *schema.ResourceData) error { - v := d.Get("versioning").([]interface{}) - bucket := d.Get("bucket").(string) - vc := &s3.VersioningConfiguration{} - - if len(v) > 0 { - c := v[0].(map[string]interface{}) - - if c["enabled"].(bool) { - vc.Status = aws.String(s3.BucketVersioningStatusEnabled) - } else { - vc.Status = aws.String(s3.BucketVersioningStatusSuspended) - } - - if c["mfa_delete"].(bool) { - vc.MFADelete = aws.String(s3.MFADeleteEnabled) - } else { - vc.MFADelete = aws.String(s3.MFADeleteDisabled) - } - - } else { - vc.Status = aws.String(s3.BucketVersioningStatusSuspended) - } - - i := &s3.PutBucketVersioningInput{ - Bucket: aws.String(bucket), - VersioningConfiguration: vc, - } - log.Printf("[DEBUG] S3 put bucket versioning: %#v", i) - - _, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.PutBucketVersioning(i) - }) - if err != nil { - return fmt.Errorf("Error putting S3 versioning: %s", err) - } - - return nil -} - -func resourceS3BucketLoggingUpdate(s3conn *s3.S3, d *schema.ResourceData) error { - logging := d.Get("logging").(*schema.Set).List() - bucket := d.Get("bucket").(string) - loggingStatus := &s3.BucketLoggingStatus{} - - if len(logging) > 0 { - c := logging[0].(map[string]interface{}) - - loggingEnabled := &s3.LoggingEnabled{} - if val, ok := c["target_bucket"]; ok { - loggingEnabled.TargetBucket = aws.String(val.(string)) - } - if val, ok := c["target_prefix"]; ok { - loggingEnabled.TargetPrefix = aws.String(val.(string)) - } - - loggingStatus.LoggingEnabled = loggingEnabled - } - - i := &s3.PutBucketLoggingInput{ - Bucket: aws.String(bucket), - BucketLoggingStatus: loggingStatus, - } - log.Printf("[DEBUG] S3 put bucket logging: %#v", i) - - _, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) { - return s3conn.PutBucketLogging(i) - }) - if err != nil { - return fmt.Errorf("Error putting S3 logging: %s", err) - } - - return nil -} - -func resourceAwsS3BucketLifecycleUpdate(s3conn *s3.S3, d *schema.ResourceData) error { - bucket := d.Get("bucket").(string) - - lifecycleRules := d.Get("lifecycle_rule").([]interface{}) - - //fmt.Printf("lifecycleRules=%+v.\n", lifecycleRules) - if len(lifecycleRules) == 0 { - i := &s3.DeleteBucketLifecycleInput{ - Bucket: aws.String(bucket), - } - - //lintignore:R006 - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - if _, err := s3conn.DeleteBucketLifecycle(i); err != nil { - return resource.NonRetryableError(err) - } - return nil - }) - if err != nil { - return fmt.Errorf("Error removing S3 lifecycle: %s", err) - } - return nil - } - - rules := make([]*s3.LifecycleRule, 0, len(lifecycleRules)) - - for i, lifecycleRule := range lifecycleRules { - r := lifecycleRule.(map[string]interface{}) - - rule := &s3.LifecycleRule{} - - // OTC only supports deprecated location for this. - rule.SetPrefix(r["prefix"].(string)) - - // ID - if val, ok := r["id"].(string); ok && val != "" { - rule.ID = aws.String(val) - } else { - rule.ID = aws.String(resource.PrefixedUniqueId("tf-s3-lifecycle-")) - } - - // Enabled - if val, ok := r["enabled"].(bool); ok && val { - rule.Status = aws.String(s3.ExpirationStatusEnabled) - } else { - rule.Status = aws.String(s3.ExpirationStatusDisabled) - } - - // AbortIncompleteMultipartUpload - if val, ok := r["abort_incomplete_multipart_upload_days"].(int); ok && val > 0 { - rule.AbortIncompleteMultipartUpload = &s3.AbortIncompleteMultipartUpload{ - DaysAfterInitiation: aws.Int64(int64(val)), - } - } - - // Expiration - expiration := d.Get(fmt.Sprintf("lifecycle_rule.%d.expiration", i)).(*schema.Set).List() - if len(expiration) > 0 { - e := expiration[0].(map[string]interface{}) - i := &s3.LifecycleExpiration{} - - if val, ok := e["date"].(string); ok && val != "" { - t, err := time.Parse(time.RFC3339, fmt.Sprintf("%sT00:00:00Z", val)) - if err != nil { - return fmt.Errorf("Error Parsing Swift S3 Bucket Lifecycle Expiration Date: %s", err.Error()) - } - i.Date = aws.Time(t) - } else if val, ok := e["days"].(int); ok && val > 0 { - i.Days = aws.Int64(int64(val)) - } else if val, ok := e["expired_object_delete_marker"].(bool); ok { - i.ExpiredObjectDeleteMarker = aws.Bool(val) - } - rule.Expiration = i - } - - // NoncurrentVersionExpiration - nc_expiration := d.Get(fmt.Sprintf("lifecycle_rule.%d.noncurrent_version_expiration", i)).(*schema.Set).List() - if len(nc_expiration) > 0 { - e := nc_expiration[0].(map[string]interface{}) - - if val, ok := e["days"].(int); ok && val > 0 { - rule.NoncurrentVersionExpiration = &s3.NoncurrentVersionExpiration{ - NoncurrentDays: aws.Int64(int64(val)), - } - } - } - - rules = append(rules, rule) - } - - //fmt.Printf("Rules=%+v.\n", rules) - i := &s3.PutBucketLifecycleConfigurationInput{ - Bucket: aws.String(bucket), - LifecycleConfiguration: &s3.BucketLifecycleConfiguration{ - Rules: rules, - }, - } - //fmt.Printf("PutBucketLifecycleConfigurationInput=%+v.\n", i) - - //lintignore:R006 - err := resource.Retry(1*time.Minute, func() *resource.RetryError { - if _, err := s3conn.PutBucketLifecycleConfiguration(i); err != nil { - return resource.NonRetryableError(err) - } - return nil - }) - if err != nil { - return fmt.Errorf("Error putting S3 lifecycle: %s", err) - } - - return nil -} - -func normalizeRoutingRules(w []*s3.RoutingRule) (string, error) { - withNulls, err := json.Marshal(w) - if err != nil { - return "", err - } - - var rules []map[string]interface{} - if err := json.Unmarshal(withNulls, &rules); err != nil { - return "", err - } - - var cleanRules []map[string]interface{} - for _, rule := range rules { - cleanRules = append(cleanRules, removeNil(rule)) - } - - withoutNulls, err := json.Marshal(cleanRules) - if err != nil { - return "", err - } - - return string(withoutNulls), nil -} - -func removeNil(data map[string]interface{}) map[string]interface{} { - withoutNil := make(map[string]interface{}) - - for k, v := range data { - if v == nil { - continue - } - - switch v.(type) { - case map[string]interface{}: - withoutNil[k] = removeNil(v.(map[string]interface{})) - default: - withoutNil[k] = v - } - } - - return withoutNil -} - -// DEPRECATED. Please consider using `normalizeJsonString` function instead. -func normalizeJson(jsonString interface{}) string { - if jsonString == nil || jsonString == "" { - return "" - } - var j interface{} - err := json.Unmarshal([]byte(jsonString.(string)), &j) - if err != nil { - return fmt.Sprintf("Error parsing JSON: %s", err) - } - b, _ := json.Marshal(j) - return string(b[:]) -} - -func normalizeRegion(region string) string { - // Default to us-east-1 if the bucket doesn't have a region: - // http://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETlocation.html - if region == "" { - region = "us-east-1" - } - - return region -} - -// validateS3BucketName validates any S3 bucket name that is not inside the us-east-1 region. -// Buckets outside of this region have to be DNS-compliant. After the same restrictions are -// applied to buckets in the us-east-1 region, this function can be refactored as a SchemaValidateFunc -func validateS3BucketName(value string, region string) error { - if region != "us-east-1" { - if (len(value) < 3) || (len(value) > 63) { - return fmt.Errorf("%q must contain from 3 to 63 characters", value) - } - if !regexp.MustCompile(`^[0-9a-z-.]+$`).MatchString(value) { - return fmt.Errorf("only lowercase alphanumeric characters and hyphens allowed in %q", value) - } - if regexp.MustCompile(`^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`).MatchString(value) { - return fmt.Errorf("%q must not be formatted as an IP address", value) - } - if strings.HasPrefix(value, `.`) { - return fmt.Errorf("%q cannot start with a period", value) - } - if strings.HasSuffix(value, `.`) { - return fmt.Errorf("%q cannot end with a period", value) - } - if strings.Contains(value, `..`) { - return fmt.Errorf("%q can be only one period between labels", value) - } - } else { - if len(value) > 255 { - return fmt.Errorf("%q must contain less than 256 characters", value) - } - if !regexp.MustCompile(`^[0-9a-zA-Z-._]+$`).MatchString(value) { - return fmt.Errorf("only alphanumeric characters, hyphens, periods, and underscores allowed in %q", value) - } - } - return nil -} - -func expirationHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - if v, ok := m["date"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - if v, ok := m["days"]; ok { - buf.WriteString(fmt.Sprintf("%d-", v.(int))) - } - if v, ok := m["expired_object_delete_marker"]; ok { - buf.WriteString(fmt.Sprintf("%t-", v.(bool))) - } - return hashcode.String(buf.String()) -} - -func transitionHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - if v, ok := m["date"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - if v, ok := m["days"]; ok { - buf.WriteString(fmt.Sprintf("%d-", v.(int))) - } - if v, ok := m["storage_class"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - return hashcode.String(buf.String()) -} - -func rulesHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - - if v, ok := m["id"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - if v, ok := m["prefix"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - if v, ok := m["status"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - return hashcode.String(buf.String()) -} - -func destinationHash(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - - if v, ok := m["bucket"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - if v, ok := m["storage_class"]; ok { - buf.WriteString(fmt.Sprintf("%s-", v.(string))) - } - return hashcode.String(buf.String()) -} - -type S3Website struct { - Endpoint, Domain string -} diff --git a/huaweicloud/resource_huaweicloud_s3_bucket_object.go b/huaweicloud/resource_huaweicloud_s3_bucket_object.go deleted file mode 100644 index 526c69ea6f..0000000000 --- a/huaweicloud/resource_huaweicloud_s3_bucket_object.go +++ /dev/null @@ -1,361 +0,0 @@ -package huaweicloud - -import ( - "bytes" - "fmt" - "io" - "log" - "os" - "sort" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/mitchellh/go-homedir" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/s3" -) - -func resourceS3BucketObject() *schema.Resource { - return &schema.Resource{ - Create: resourceS3BucketObjectPut, - Read: resourceS3BucketObjectRead, - Update: resourceS3BucketObjectPut, - Delete: resourceS3BucketObjectDelete, - DeprecationMessage: "use huaweicloud_obs_bucket_object resource instead", - - Schema: map[string]*schema.Schema{ - "bucket": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "acl": { - Type: schema.TypeString, - Default: "private", - Optional: true, - ValidateFunc: validateS3BucketObjectAclType, - }, - - "cache_control": { - Type: schema.TypeString, - Optional: true, - }, - - "content_disposition": { - Type: schema.TypeString, - Optional: true, - }, - - "content_encoding": { - Type: schema.TypeString, - Optional: true, - }, - - "content_language": { - Type: schema.TypeString, - Optional: true, - }, - - "content_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - - "key": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "source": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"content"}, - }, - - "content": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"source"}, - }, - - "server_side_encryption": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validateS3BucketObjectServerSideEncryption, - Computed: true, - }, - - "sse_kms_key_id": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - - "etag": { - Type: schema.TypeString, - // This will conflict with SSE-C and SSE-KMS encryption and multi-part upload - // if/when it's actually implemented. The Etag then won't match raw-file MD5. - // See http://docs.aws.amazon.com/AmazonS3/latest/API/RESTCommonResponseHeaders.html - Optional: true, - Computed: true, - }, - - "version_id": { - Type: schema.TypeString, - Computed: true, - }, - - "website_redirect": { - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func resourceS3BucketObjectPut(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - s3conn, err := config.computeS3conn(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - var body io.ReadSeeker - - if v, ok := d.GetOk("source"); ok { - source := v.(string) - path, err := homedir.Expand(source) - if err != nil { - return fmt.Errorf("Error expanding homedir in source (%s): %s", source, err) - } - file, err := os.Open(path) - if err != nil { - return fmt.Errorf("Error opening S3 bucket object source (%s): %s", source, err) - } - - body = file - } else if v, ok := d.GetOk("content"); ok { - content := v.(string) - body = bytes.NewReader([]byte(content)) - } else { - return fmt.Errorf("Must specify \"source\" or \"content\" field") - } - - bucket := d.Get("bucket").(string) - key := d.Get("key").(string) - - putInput := &s3.PutObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - ACL: aws.String(d.Get("acl").(string)), - Body: body, - } - - if v, ok := d.GetOk("cache_control"); ok { - putInput.CacheControl = aws.String(v.(string)) - } - - if v, ok := d.GetOk("content_type"); ok { - putInput.ContentType = aws.String(v.(string)) - } - - if v, ok := d.GetOk("content_encoding"); ok { - putInput.ContentEncoding = aws.String(v.(string)) - } - - if v, ok := d.GetOk("content_language"); ok { - putInput.ContentLanguage = aws.String(v.(string)) - } - - if v, ok := d.GetOk("content_disposition"); ok { - putInput.ContentDisposition = aws.String(v.(string)) - } - - if v, ok := d.GetOk("server_side_encryption"); ok { - putInput.ServerSideEncryption = aws.String(v.(string)) - } - - if v, ok := d.GetOk("sse_kms_key_id"); ok { - putInput.SSEKMSKeyId = aws.String(v.(string)) - } - - if v, ok := d.GetOk("website_redirect"); ok { - putInput.WebsiteRedirectLocation = aws.String(v.(string)) - } - - resp, err := s3conn.PutObject(putInput) - if err != nil { - return fmt.Errorf("Error putting object in S3 bucket (%s): %s", bucket, err) - } - - // See https://forums.aws.amazon.com/thread.jspa?threadID=44003 - d.Set("etag", strings.Trim(*resp.ETag, `"`)) - - d.Set("version_id", resp.VersionId) - d.SetId(key) - return resourceS3BucketObjectRead(d, meta) -} - -func resourceS3BucketObjectRead(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - s3conn, err := config.computeS3conn(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - bucket := d.Get("bucket").(string) - key := d.Get("key").(string) - - resp, err := s3conn.HeadObject( - &s3.HeadObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - }) - - if err != nil { - // If S3 returns a 404 Request Failure, mark the object as destroyed - if awsErr, ok := err.(awserr.RequestFailure); ok && awsErr.StatusCode() == 404 { - d.SetId("") - log.Printf("[WARN] Error Reading Object (%s), object not found (HTTP status 404)", key) - return nil - } - return err - } - log.Printf("[DEBUG] Reading S3 Bucket Object meta: %s", resp) - - d.Set("cache_control", resp.CacheControl) - d.Set("content_disposition", resp.ContentDisposition) - d.Set("content_encoding", resp.ContentEncoding) - d.Set("content_language", resp.ContentLanguage) - d.Set("content_type", resp.ContentType) - d.Set("version_id", resp.VersionId) - d.Set("server_side_encryption", resp.ServerSideEncryption) - d.Set("website_redirect", resp.WebsiteRedirectLocation) - - d.Set("etag", strings.Trim(*resp.ETag, `"`)) - - return nil -} - -func resourceS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - s3conn, err := config.computeS3conn(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - bucket := d.Get("bucket").(string) - key := d.Get("key").(string) - - if _, ok := d.GetOk("version_id"); ok { - // Bucket is versioned, we need to delete all versions - vInput := s3.ListObjectVersionsInput{ - Bucket: aws.String(bucket), - Prefix: aws.String(key), - } - out, err := s3conn.ListObjectVersions(&vInput) - if err != nil { - return fmt.Errorf("Failed listing S3 object versions: %s", err) - } - - for _, v := range out.Versions { - input := s3.DeleteObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - VersionId: v.VersionId, - } - _, err := s3conn.DeleteObject(&input) - if err != nil { - return fmt.Errorf("Error deleting S3 object version of %s:\n %s:\n %s", - key, v, err) - } - } - } else { - // Just delete the object - input := s3.DeleteObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String(key), - } - _, err := s3conn.DeleteObject(&input) - if err != nil { - return fmt.Errorf("Error deleting S3 bucket object: %s Bucket: %q Object: %q", err, bucket, key) - } - } - - return nil -} - -func validateS3BucketObjectAclType(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - - cannedAcls := map[string]bool{ - s3.ObjectCannedACLPrivate: true, - s3.ObjectCannedACLPublicRead: true, - s3.ObjectCannedACLPublicReadWrite: true, - s3.ObjectCannedACLAuthenticatedRead: true, - s3.ObjectCannedACLAwsExecRead: true, - s3.ObjectCannedACLBucketOwnerRead: true, - s3.ObjectCannedACLBucketOwnerFullControl: true, - } - - sentenceJoin := func(m map[string]bool) string { - keys := make([]string, 0, len(m)) - for k := range m { - keys = append(keys, fmt.Sprintf("%q", k)) - } - sort.Strings(keys) - - length := len(keys) - words := make([]string, length) - copy(words, keys) - - words[length-1] = fmt.Sprintf("or %s", words[length-1]) - return strings.Join(words, ", ") - } - - if _, ok := cannedAcls[value]; !ok { - errors = append(errors, fmt.Errorf( - "%q contains an invalid canned ACL type %q. Valid types are either %s", - k, value, sentenceJoin(cannedAcls))) - } - return -} - -func validateS3BucketObjectStorageClassType(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - - storageClass := map[string]bool{ - s3.StorageClassStandard: true, - s3.StorageClassReducedRedundancy: true, - s3.StorageClassStandardIa: true, - } - - if _, ok := storageClass[value]; !ok { - errors = append(errors, fmt.Errorf( - "%q contains an invalid Storage Class type %q. Valid types are either %q, %q, or %q", - k, value, s3.StorageClassStandard, s3.StorageClassReducedRedundancy, - s3.StorageClassStandardIa)) - } - return -} - -func validateS3BucketObjectServerSideEncryption(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - - serverSideEncryption := map[string]bool{ - s3.ServerSideEncryptionAes256: true, - s3.ServerSideEncryptionAwsKms: true, - } - - if _, ok := serverSideEncryption[value]; !ok { - errors = append(errors, fmt.Errorf( - "%q contains an invalid Server Side Encryption value %q. Valid values are %q and %q", - k, value, s3.ServerSideEncryptionAes256, s3.ServerSideEncryptionAwsKms)) - } - return -} diff --git a/huaweicloud/resource_huaweicloud_s3_bucket_object_test.go b/huaweicloud/resource_huaweicloud_s3_bucket_object_test.go deleted file mode 100644 index eea9120cae..0000000000 --- a/huaweicloud/resource_huaweicloud_s3_bucket_object_test.go +++ /dev/null @@ -1,565 +0,0 @@ -package huaweicloud - -import ( - "fmt" - "io/ioutil" - "os" - "reflect" - "sort" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/s3" -) - -func TestAccS3BucketObject_source(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-source") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpFile.Name()) - - rInt := acctest.RandInt() - // first write some data to the tempfile just so it's not 0 bytes. - err = ioutil.WriteFile(tmpFile.Name(), []byte("{anything will do }"), 0644) - if err != nil { - t.Fatal(err) - } - var obj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketObjectDestroy, - Steps: []resource.TestStep{ - { - Config: testAccS3BucketObjectConfigSource(rInt, tmpFile.Name()), - Check: testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &obj), - }, - }, - }) -} - -func TestAccS3BucketObject_content(t *testing.T) { - rInt := acctest.RandInt() - var obj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketObjectDestroy, - Steps: []resource.TestStep{ - { - PreConfig: func() {}, - Config: testAccS3BucketObjectConfigContent(rInt), - Check: testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &obj), - }, - }, - }) -} - -func TestAccS3BucketObject_withContentCharacteristics(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-content-characteristics") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpFile.Name()) - - rInt := acctest.RandInt() - // first write some data to the tempfile just so it's not 0 bytes. - err = ioutil.WriteFile(tmpFile.Name(), []byte("{anything will do }"), 0644) - if err != nil { - t.Fatal(err) - } - - var obj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketObjectDestroy, - Steps: []resource.TestStep{ - { - Config: testAccS3BucketObjectConfig_withContentCharacteristics(rInt, tmpFile.Name()), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &obj), - resource.TestCheckResourceAttr( - "huaweicloud_s3_bucket_object.object", "content_type", "binary/octet-stream"), - resource.TestCheckResourceAttr( - "huaweicloud_s3_bucket_object.object", "website_redirect", "http://google.com"), - ), - }, - }, - }) -} - -func TestAccS3BucketObject_updates(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-updates") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpFile.Name()) - - rInt := acctest.RandInt() - err = ioutil.WriteFile(tmpFile.Name(), []byte("initial object state"), 0644) - if err != nil { - t.Fatal(err) - } - var obj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketObjectDestroy, - Steps: []resource.TestStep{ - { - Config: testAccS3BucketObjectConfig_updates(rInt, tmpFile.Name()), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &obj), - resource.TestCheckResourceAttr("huaweicloud_s3_bucket_object.object", "etag", "647d1d58e1011c743ec67d5e8af87b53"), - ), - }, - { - PreConfig: func() { - err = ioutil.WriteFile(tmpFile.Name(), []byte("modified object"), 0644) - if err != nil { - t.Fatal(err) - } - }, - Config: testAccS3BucketObjectConfig_updates(rInt, tmpFile.Name()), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &obj), - resource.TestCheckResourceAttr("huaweicloud_s3_bucket_object.object", "etag", "1c7fd13df1515c2a13ad9eb068931f09"), - ), - }, - }, - }) -} - -func TestAccS3BucketObject_updatesWithVersioning(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-updates-w-versions") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpFile.Name()) - - rInt := acctest.RandInt() - err = ioutil.WriteFile(tmpFile.Name(), []byte("initial versioned object state"), 0644) - if err != nil { - t.Fatal(err) - } - - var originalObj, modifiedObj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketObjectDestroy, - Steps: []resource.TestStep{ - { - Config: testAccS3BucketObjectConfig_updatesWithVersioning(rInt, tmpFile.Name()), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &originalObj), - resource.TestCheckResourceAttr("huaweicloud_s3_bucket_object.object", "etag", "cee4407fa91906284e2a5e5e03e86b1b"), - ), - }, - { - PreConfig: func() { - err = ioutil.WriteFile(tmpFile.Name(), []byte("modified versioned object"), 0644) - if err != nil { - t.Fatal(err) - } - }, - Config: testAccS3BucketObjectConfig_updatesWithVersioning(rInt, tmpFile.Name()), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists("huaweicloud_s3_bucket_object.object", &modifiedObj), - resource.TestCheckResourceAttr("huaweicloud_s3_bucket_object.object", "etag", "00b8c73b1b50e7cc932362c7225b8e29"), - testAccCheckS3BucketObjectVersionIdDiffers(&originalObj, &modifiedObj), - ), - }, - }, - }) -} - -func testAccCheckS3BucketObjectVersionIdDiffers(first, second *s3.GetObjectOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - if first.VersionId == nil { - return fmt.Errorf("Expected first object to have VersionId: %s", first) - } - if second.VersionId == nil { - return fmt.Errorf("Expected second object to have VersionId: %s", second) - } - - if *first.VersionId == *second.VersionId { - return fmt.Errorf("Expected Version IDs to differ, but they are equal (%s)", *first.VersionId) - } - - return nil - } -} - -func testAccCheckS3BucketObjectDestroy(s *terraform.State) error { - config := testAccProvider.Meta().(*Config) - s3conn, err := config.computeS3conn(HW_REGION_NAME) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - for _, rs := range s.RootModule().Resources { - if rs.Type != "huaweicloud_s3_bucket_object" { - continue - } - - _, err := s3conn.HeadObject( - &s3.HeadObjectInput{ - Bucket: aws.String(rs.Primary.Attributes["bucket"]), - Key: aws.String(rs.Primary.Attributes["key"]), - IfMatch: aws.String(rs.Primary.Attributes["etag"]), - }) - if err == nil { - return fmt.Errorf("Swift S3 Object still exists: %s", rs.Primary.ID) - } - } - return nil -} - -func testAccCheckS3BucketObjectExists(n string, obj *s3.GetObjectOutput) 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 S3 Bucket Object ID is set") - } - - config := testAccProvider.Meta().(*Config) - s3conn, err := config.computeS3conn(HW_REGION_NAME) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - out, err := s3conn.GetObject( - &s3.GetObjectInput{ - Bucket: aws.String(rs.Primary.Attributes["bucket"]), - Key: aws.String(rs.Primary.Attributes["key"]), - IfMatch: aws.String(rs.Primary.Attributes["etag"]), - }) - if err != nil { - return fmt.Errorf("S3Bucket Object error: %s", err) - } - - *obj = *out - - return nil - } -} - -func TestAccS3BucketObject_sse(t *testing.T) { - tmpFile, err := ioutil.TempFile("", "tf-acc-s3-obj-source-sse") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpFile.Name()) - - // first write some data to the tempfile just so it's not 0 bytes. - err = ioutil.WriteFile(tmpFile.Name(), []byte("{anything will do}"), 0644) - if err != nil { - t.Fatal(err) - } - - rInt := acctest.RandInt() - var obj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketObjectDestroy, - Steps: []resource.TestStep{ - { - PreConfig: func() {}, - Config: testAccS3BucketObjectConfig_withSSE(rInt, tmpFile.Name()), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists( - "huaweicloud_s3_bucket_object.object", - &obj), - testAccCheckS3BucketObjectSSE( - "huaweicloud_s3_bucket_object.object", - "aws:kms"), - ), - }, - }, - }) -} - -func TestAccS3BucketObject_acl(t *testing.T) { - rInt := acctest.RandInt() - var obj s3.GetObjectOutput - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketObjectDestroy, - Steps: []resource.TestStep{ - { - Config: testAccS3BucketObjectConfig_acl(rInt, "private"), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists( - "huaweicloud_s3_bucket_object.object", &obj), - resource.TestCheckResourceAttr( - "huaweicloud_s3_bucket_object.object", - "acl", - "private"), - testAccCheckS3BucketObjectAcl( - "huaweicloud_s3_bucket_object.object", - []string{"FULL_CONTROL"}), - ), - }, - { - Config: testAccS3BucketObjectConfig_acl(rInt, "public-read"), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketObjectExists( - "huaweicloud_s3_bucket_object.object", - &obj), - resource.TestCheckResourceAttr( - "huaweicloud_s3_bucket_object.object", - "acl", - "public-read"), - testAccCheckS3BucketObjectAcl( - "huaweicloud_s3_bucket_object.object", - []string{"FULL_CONTROL", "READ"}), - ), - }, - }, - }) -} - -func testAccCheckS3BucketObjectAcl(n string, expectedPerms []string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, _ := s.RootModule().Resources[n] - config := testAccProvider.Meta().(*Config) - s3conn, err := config.computeS3conn(HW_REGION_NAME) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - out, err := s3conn.GetObjectAcl(&s3.GetObjectAclInput{ - Bucket: aws.String(rs.Primary.Attributes["bucket"]), - Key: aws.String(rs.Primary.Attributes["key"]), - }) - - if err != nil { - return fmt.Errorf("GetObjectAcl error: %v", err) - } - - var perms []string - for _, v := range out.Grants { - perms = append(perms, *v.Permission) - } - sort.Strings(perms) - - if !reflect.DeepEqual(perms, expectedPerms) { - return fmt.Errorf("Expected ACL permissions to be %v, got %v", expectedPerms, perms) - } - - return nil - } -} - -func TestResourceS3BucketObjectAcl_validation(t *testing.T) { - _, errors := validateS3BucketObjectAclType("incorrect", "acl") - if len(errors) == 0 { - t.Fatalf("Expected to trigger a validation error") - } - - var testCases = []struct { - Value string - ErrCount int - }{ - { - Value: "public-read", - ErrCount: 0, - }, - { - Value: "public-read-write", - ErrCount: 0, - }, - } - - for _, tc := range testCases { - _, errors := validateS3BucketObjectAclType(tc.Value, "acl") - if len(errors) != tc.ErrCount { - t.Fatalf("Expected not to trigger a validation error") - } - } -} - -func TestResourceS3BucketObjectStorageClass_validation(t *testing.T) { - _, errors := validateS3BucketObjectStorageClassType("incorrect", "storage_class") - if len(errors) == 0 { - t.Fatalf("Expected to trigger a validation error") - } - - var testCases = []struct { - Value string - ErrCount int - }{ - { - Value: "STANDARD", - ErrCount: 0, - }, - { - Value: "REDUCED_REDUNDANCY", - ErrCount: 0, - }, - } - - for _, tc := range testCases { - _, errors := validateS3BucketObjectStorageClassType(tc.Value, "storage_class") - if len(errors) != tc.ErrCount { - t.Fatalf("Expected not to trigger a validation error") - } - } -} - -func testAccCheckS3BucketObjectSSE(n, expectedSSE string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, _ := s.RootModule().Resources[n] - config := testAccProvider.Meta().(*Config) - s3conn, err := config.computeS3conn(HW_REGION_NAME) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - out, err := s3conn.HeadObject(&s3.HeadObjectInput{ - Bucket: aws.String(rs.Primary.Attributes["bucket"]), - Key: aws.String(rs.Primary.Attributes["key"]), - }) - - if err != nil { - return fmt.Errorf("HeadObject error: %v", err) - } - - if out.ServerSideEncryption == nil { - return fmt.Errorf("Expected a non %v Server Side Encryption.", out.ServerSideEncryption) - } - - sse := *out.ServerSideEncryption - if sse != expectedSSE { - return fmt.Errorf("Expected Server Side Encryption %v, got %v.", - expectedSSE, sse) - } - - return nil - } -} - -func testAccS3BucketObjectConfigSource(randInt int, source string) string { - return fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket" { - bucket = "tf-object-test-bucket-%d" -} -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket.bucket}" - key = "test-key" - source = "%s" - content_type = "binary/octet-stream" -} -`, randInt, source) -} - -func testAccS3BucketObjectConfig_withContentCharacteristics(randInt int, source string) string { - return fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket_2" { - bucket = "tf-object-test-bucket-%d" -} - -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket_2.bucket}" - key = "test-key" - source = "%s" - content_language = "en" - content_type = "binary/octet-stream" - website_redirect = "http://google.com" -} -`, randInt, source) -} - -func testAccS3BucketObjectConfigContent(randInt int) string { - return fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket" { - bucket = "tf-object-test-bucket-%d" -} -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket.bucket}" - key = "test-key" - content = "some_bucket_content" -} -`, randInt) -} - -func testAccS3BucketObjectConfig_updates(randInt int, source string) string { - return fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket_3" { - bucket = "tf-object-test-bucket-%d" -} - -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket_3.bucket}" - key = "updateable-key" - source = "%s" - etag = "${md5(file("%s"))}" -} -`, randInt, source, source) -} - -func testAccS3BucketObjectConfig_updatesWithVersioning(randInt int, source string) string { - return fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket_3" { - bucket = "tf-object-test-bucket-%d" - versioning { - enabled = true - } -} - -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket_3.bucket}" - key = "updateable-key" - source = "%s" - etag = "${md5(file("%s"))}" -} -`, randInt, source, source) -} - -func testAccS3BucketObjectConfig_withSSE(randInt int, source string) string { - return fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket" { - bucket = "tf-object-test-bucket-%d" -} - -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket.bucket}" - key = "test-key" - source = "%s" - server_side_encryption = "aws:kms" -} -`, randInt, source) -} - -func testAccS3BucketObjectConfig_acl(randInt int, acl string) string { - return fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "object_bucket" { - bucket = "tf-object-test-bucket-%d" -} -resource "huaweicloud_s3_bucket_object" "object" { - bucket = "${huaweicloud_s3_bucket.object_bucket.bucket}" - key = "test-key" - content = "some_bucket_content" - acl = "%s" -} -`, randInt, acl) -} diff --git a/huaweicloud/resource_huaweicloud_s3_bucket_policy.go b/huaweicloud/resource_huaweicloud_s3_bucket_policy.go deleted file mode 100644 index ce7a8eb84e..0000000000 --- a/huaweicloud/resource_huaweicloud_s3_bucket_policy.go +++ /dev/null @@ -1,123 +0,0 @@ -package huaweicloud - -import ( - "fmt" - "log" - "time" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" -) - -func resourceS3BucketPolicy() *schema.Resource { - return &schema.Resource{ - Create: resourceS3BucketPolicyPut, - Read: resourceS3BucketPolicyRead, - Update: resourceS3BucketPolicyPut, - Delete: resourceS3BucketPolicyDelete, - DeprecationMessage: "use huaweicloud_obs_bucket_policy resource instead", - - Schema: map[string]*schema.Schema{ - "bucket": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "policy": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validateJsonString, - DiffSuppressFunc: suppressEquivalentAwsPolicyDiffs, - }, - }, - } -} - -func resourceS3BucketPolicyPut(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - s3conn, err := config.computeS3conn(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - bucket := d.Get("bucket").(string) - policy := d.Get("policy").(string) - - d.SetId(bucket) - - log.Printf("[DEBUG] S3 bucket: %s, put policy: %s", bucket, policy) - - params := &s3.PutBucketPolicyInput{ - Bucket: aws.String(bucket), - Policy: aws.String(policy), - } - //lintignore:R006 - err = resource.Retry(1*time.Minute, func() *resource.RetryError { - if _, err := s3conn.PutBucketPolicy(params); err != nil { - if awserr, ok := err.(awserr.Error); ok { - if awserr.Code() == "MalformedPolicy" { - return resource.RetryableError(awserr) - } - } - return resource.NonRetryableError(err) - } - return nil - }) - - if err != nil { - return fmt.Errorf("Error putting S3 policy: %s", err) - } - - return nil -} - -func resourceS3BucketPolicyRead(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - s3conn, err := config.computeS3conn(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - log.Printf("[DEBUG] S3 bucket policy, read for bucket: %s", d.Id()) - pol, err := s3conn.GetBucketPolicy(&s3.GetBucketPolicyInput{ - Bucket: aws.String(d.Id()), - }) - - v := "" - if err == nil && pol.Policy != nil { - v = *pol.Policy - } - if err := d.Set("policy", v); err != nil { - return err - } - - return nil -} - -func resourceS3BucketPolicyDelete(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - s3conn, err := config.computeS3conn(GetRegion(d, config)) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - bucket := d.Get("bucket").(string) - - log.Printf("[DEBUG] S3 bucket: %s, delete policy", bucket) - _, err = s3conn.DeleteBucketPolicy(&s3.DeleteBucketPolicyInput{ - Bucket: aws.String(bucket), - }) - - if err != nil { - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoSuchBucket" { - return nil - } - return fmt.Errorf("Error deleting S3 policy: %s", err) - } - - return nil -} diff --git a/huaweicloud/resource_huaweicloud_s3_bucket_policy_test.go b/huaweicloud/resource_huaweicloud_s3_bucket_policy_test.go deleted file mode 100644 index dc85b54caf..0000000000 --- a/huaweicloud/resource_huaweicloud_s3_bucket_policy_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package huaweicloud - -import ( - "fmt" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/terraform" - "github.com/jen20/awspolicyequivalence" -) - -func TestAccS3BucketPolicy_basic(t *testing.T) { - name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt()) - - expectedPolicyText := fmt.Sprintf( - `{"Version":"2008-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:*"],"Resource":["arn:aws:s3:::%s/*","arn:aws:s3:::%s"]}]}`, - name, name) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketDestroy, - Steps: []resource.TestStep{ - { - Config: testAccS3BucketPolicyConfig(name), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketExists("huaweicloud_s3_bucket.bucket"), - testAccCheckS3BucketHasPolicy("huaweicloud_s3_bucket.bucket", expectedPolicyText), - ), - }, - }, - }) -} - -func TestAccS3BucketPolicy_policyUpdate(t *testing.T) { - name := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt()) - - expectedPolicyText1 := fmt.Sprintf( - `{"Version":"2008-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:*"],"Resource":["arn:aws:s3:::%s/*","arn:aws:s3:::%s"]}]}`, - name, name) - - expectedPolicyText2 := fmt.Sprintf( - `{"Version":"2008-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:DeleteBucket", "s3:ListBucket", "s3:ListBucketVersions"], "Resource":["arn:aws:s3:::%s/*","arn:aws:s3:::%s"]}]}`, - name, name) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheckDeprecated(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckS3BucketDestroy, - Steps: []resource.TestStep{ - { - Config: testAccS3BucketPolicyConfig(name), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketExists("huaweicloud_s3_bucket.bucket"), - testAccCheckS3BucketHasPolicy("huaweicloud_s3_bucket.bucket", expectedPolicyText1), - ), - }, - - { - Config: testAccS3BucketPolicyConfig_updated(name), - Check: resource.ComposeTestCheckFunc( - testAccCheckS3BucketExists("huaweicloud_s3_bucket.bucket"), - testAccCheckS3BucketHasPolicy("huaweicloud_s3_bucket.bucket", expectedPolicyText2), - ), - }, - }, - }) -} - -func testAccCheckS3BucketHasPolicy(n string, expectedPolicyText 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 S3 Bucket ID is set") - } - - config := testAccProvider.Meta().(*Config) - conn, err := config.computeS3conn(HW_REGION_NAME) - if err != nil { - return fmt.Errorf("Error creating HuaweiCloud s3 client: %s", err) - } - - policy, err := conn.GetBucketPolicy(&s3.GetBucketPolicyInput{ - Bucket: aws.String(rs.Primary.ID), - }) - if err != nil { - return fmt.Errorf("GetBucketPolicy error: %v", err) - } - - actualPolicyText := *policy.Policy - - equivalent, err := awspolicy.PoliciesAreEquivalent(actualPolicyText, expectedPolicyText) - if err != nil { - return fmt.Errorf("Error testing policy equivalence: %s", err) - } - if !equivalent { - return fmt.Errorf("Non-equivalent policy error:\n\nexpected: %s\n\n got: %s\n", - expectedPolicyText, actualPolicyText) - } - - return nil - } -} - -func testAccS3BucketPolicyConfig(bucketName string) string { - return fmt.Sprintf(` -resource "huaweicloud_s3_bucket" "bucket" { - bucket = "%s" - #tags { - # TestName = "TestAccAWSS3BucketPolicy_basic" - #} -} - -resource "huaweicloud_s3_bucket_policy" "bucket" { - bucket = "${huaweicloud_s3_bucket.bucket.bucket}" - policy =< 0 { - log.Printf("[DEBUG] Removing tags: %#v", remove) - _, err := retryOnAwsCodes([]string{"NoSuchBucket", "OperationAborted"}, func() (interface{}, error) { - return conn.DeleteBucketTagging(&s3.DeleteBucketTaggingInput{ - Bucket: aws.String(d.Get("bucket").(string)), - }) - }) - if err != nil { - return err - } - } - if len(create) > 0 { - log.Printf("[DEBUG] Creating tags: %#v", create) - req := &s3.PutBucketTaggingInput{ - Bucket: aws.String(d.Get("bucket").(string)), - Tagging: &s3.Tagging{ - TagSet: create, - }, - } - - _, err := retryOnAwsCodes([]string{"NoSuchBucket", "OperationAborted"}, func() (interface{}, error) { - return conn.PutBucketTagging(req) - }) - if err != nil { - return err - } - } - } - - return nil -} - -// diffTags takes our tags locally and the ones remotely and returns -// the set of tags that must be created, and the set of tags that must -// be destroyed. -func diffTagsS3(oldTags, newTags []*s3.Tag) ([]*s3.Tag, []*s3.Tag) { - // First, we're creating everything we have - create := make(map[string]interface{}) - for _, t := range newTags { - create[*t.Key] = *t.Value - } - - // Build the list of what to remove - var remove []*s3.Tag - for _, t := range oldTags { - old, ok := create[*t.Key] - if !ok || old != *t.Value { - // Delete it! - remove = append(remove, t) - } - } - - return tagsFromMapS3(create), remove -} - -// tagsFromMap returns the tags for the given map of data. -func tagsFromMapS3(m map[string]interface{}) []*s3.Tag { - result := make([]*s3.Tag, 0, len(m)) - for k, v := range m { - t := &s3.Tag{ - Key: aws.String(k), - Value: aws.String(v.(string)), - } - if !tagIgnoredS3(t) { - result = append(result, t) - } - } - - return result -} - -// tagsToMap turns the list of tags into a map. -func tagsToMapS3(ts []*s3.Tag) map[string]string { - result := make(map[string]string) - for _, t := range ts { - if !tagIgnoredS3(t) { - result[*t.Key] = *t.Value - } - } - - return result -} - -// return a slice of s3 tags associated with the given s3 bucket. Essentially -// s3.GetBucketTagging, except returns an empty slice instead of an error when -// there are no tags. -func getTagSetS3(s3conn *s3.S3, bucket string) ([]*s3.Tag, error) { - request := &s3.GetBucketTaggingInput{ - Bucket: aws.String(bucket), - } - - response, err := s3conn.GetBucketTagging(request) - if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "NoSuchTagSet" { - // There is no tag set associated with the bucket. - return []*s3.Tag{}, nil - } else if err != nil { - return nil, err - } - - return response.TagSet, nil -} - -// compare a tag against a list of strings and checks if it should -// be ignored or not -func tagIgnoredS3(t *s3.Tag) bool { - filter := []string{"^aws:"} - for _, v := range filter { - log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) - if r, _ := regexp.MatchString(v, *t.Key); r == true { - log.Printf("[DEBUG] Found AWS specific tag %s (val: %s), ignoring.\n", *t.Key, *t.Value) - return true - } - } - return false -} diff --git a/huaweicloud/util.go b/huaweicloud/util.go index b36c4e4ea3..4255ba010e 100644 --- a/huaweicloud/util.go +++ b/huaweicloud/util.go @@ -211,3 +211,22 @@ func removeDuplicateElem(s []string) []string { } return result } + +func removeNil(data map[string]interface{}) map[string]interface{} { + withoutNil := make(map[string]interface{}) + + for k, v := range data { + if v == nil { + continue + } + + switch v.(type) { + case map[string]interface{}: + withoutNil[k] = removeNil(v.(map[string]interface{})) + default: + withoutNil[k] = v + } + } + + return withoutNil +} diff --git a/huaweicloud/validators.go b/huaweicloud/validators.go index 357505143e..eaf8d67d2c 100644 --- a/huaweicloud/validators.go +++ b/huaweicloud/validators.go @@ -5,7 +5,6 @@ import ( "net" "regexp" "strings" - "time" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -34,17 +33,6 @@ func ValidateIntRange(v interface{}, k string, l int, h int) (ws []string, error return } -func validateS3BucketLifecycleTimestamp(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - _, err := time.Parse(time.RFC3339, fmt.Sprintf("%sT00:00:00Z", value)) - if err != nil { - errors = append(errors, fmt.Errorf( - "%q cannot be parsed as RFC3339 Timestamp Format", value)) - } - - return -} - func validateTrueOnly(v interface{}, k string) (ws []string, errors []error) { if b, ok := v.(bool); ok && b { return @@ -56,24 +44,6 @@ func validateTrueOnly(v interface{}, k string) (ws []string, errors []error) { return } -func validateS3BucketLifecycleExpirationDays(v interface{}, k string) (ws []string, errors []error) { - if v.(int) <= 0 { - errors = append(errors, fmt.Errorf( - "%q must be greater than 0", k)) - } - - return -} - -func validateS3BucketLifecycleRuleId(v interface{}, k string) (ws []string, errors []error) { - value := v.(string) - if len(value) > 255 { - errors = append(errors, fmt.Errorf( - "%q cannot exceed 255 characters", k)) - } - return -} - func validateJsonString(v interface{}, k string) (ws []string, errors []error) { if _, err := normalizeJsonString(v); err != nil { errors = append(errors, fmt.Errorf("%q contains an invalid JSON: %s", k, err)) diff --git a/vendor/modules.txt b/vendor/modules.txt index e57e0f0f4f..519c10fb59 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -15,7 +15,6 @@ github.com/apparentlymart/go-textseg/textseg # github.com/armon/go-radix v1.0.0 github.com/armon/go-radix # github.com/aws/aws-sdk-go v1.25.3 -## explicit github.com/aws/aws-sdk-go/aws github.com/aws/aws-sdk-go/aws/awserr github.com/aws/aws-sdk-go/aws/awsutil @@ -82,7 +81,6 @@ github.com/googleapis/gax-go/v2 ## explicit github.com/hashicorp/errwrap # github.com/hashicorp/go-cleanhttp v0.5.1 -## explicit github.com/hashicorp/go-cleanhttp # github.com/hashicorp/go-getter v1.4.0 github.com/hashicorp/go-getter @@ -377,7 +375,6 @@ github.com/mitchellh/colorstring # github.com/mitchellh/copystructure v1.0.0 github.com/mitchellh/copystructure # github.com/mitchellh/go-homedir v1.1.0 -## explicit github.com/mitchellh/go-homedir # github.com/mitchellh/go-testing-interface v1.0.0 github.com/mitchellh/go-testing-interface