Skip to content

Commit

Permalink
New Resource: aws_s3control_bucket
Browse files Browse the repository at this point in the history
Reference: #15413

Output from acceptance testing:

```
Pending
```
  • Loading branch information
bflad committed Oct 6, 2020
1 parent afafdbf commit 4cdf445
Show file tree
Hide file tree
Showing 9 changed files with 599 additions and 0 deletions.
1 change: 1 addition & 0 deletions aws/internal/keyvaluetags/generators/servicetags/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ var sliceServiceNames = []string{
"route53",
"route53resolver",
"s3",
"s3control",
"sagemaker",
"secretsmanager",
"serverlessapplicationrepository",
Expand Down
90 changes: 90 additions & 0 deletions aws/internal/keyvaluetags/s3control_tags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package keyvaluetags

import (
"fmt"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/s3control"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
)

// Custom S3control tagging functions using similar formatting as other service generated code.

// S3controlBucketListTags lists S3control bucket tags.
// The identifier is the bucket ARN.
func S3controlBucketListTags(conn *s3control.S3Control, identifier string) (KeyValueTags, error) {
parsedArn, err := arn.Parse(identifier)

if err != nil {
return New(nil), fmt.Errorf("error parsing S3 Control Bucket ARN (%s): %w", identifier, err)
}

input := &s3control.GetBucketTaggingInput{
AccountId: aws.String(parsedArn.AccountID),
Bucket: aws.String(identifier),
}

output, err := conn.GetBucketTagging(input)

if tfawserr.ErrCodeEquals(err, "NoSuchTagSet") {
return New(nil), nil
}

if err != nil {
return New(nil), err
}

return S3controlKeyValueTags(output.TagSet), nil
}

// S3controlBucketUpdateTags updates S3control bucket tags.
// The identifier is the bucket ARN.
func S3controlBucketUpdateTags(conn *s3control.S3Control, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error {
parsedArn, err := arn.Parse(identifier)

if err != nil {
return fmt.Errorf("error parsing S3 Control Bucket ARN (%s): %w", identifier, err)
}

oldTags := New(oldTagsMap)
newTags := New(newTagsMap)

// We need to also consider any existing ignored tags.
allTags, err := S3controlBucketListTags(conn, identifier)

if err != nil {
return fmt.Errorf("error listing resource tags (%s): %w", identifier, err)
}

ignoredTags := allTags.Ignore(oldTags).Ignore(newTags)

if len(newTags)+len(ignoredTags) > 0 {
input := &s3control.PutBucketTaggingInput{
AccountId: aws.String(parsedArn.AccountID),
Bucket: aws.String(identifier),
Tagging: &s3control.Tagging{
TagSet: newTags.Merge(ignoredTags).S3controlTags(),
},
}

_, err := conn.PutBucketTagging(input)

if err != nil {
return fmt.Errorf("error setting resource tags (%s): %w", identifier, err)
}
} else if len(oldTags) > 0 && len(ignoredTags) == 0 {
input := &s3control.DeleteBucketTaggingInput{
AccountId: aws.String(parsedArn.AccountID),
Bucket: aws.String(identifier),
}

_, err := conn.DeleteBucketTagging(input)

if err != nil {
return fmt.Errorf("error deleting resource tags (%s): %w", identifier, err)
}
}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,8 @@ func ServiceTagType(serviceName string) string {
return "TagListEntry"
case "fms":
return "ResourceTag"
case "s3control":
return "S3Tag"
case "swf":
return "ResourceTag"
default:
Expand Down
28 changes: 28 additions & 0 deletions aws/internal/keyvaluetags/service_tags_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,7 @@ func Provider() *schema.Provider {
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
"aws_s3_bucket_metric": resourceAwsS3BucketMetric(),
"aws_s3_bucket_inventory": resourceAwsS3BucketInventory(),
"aws_s3control_bucket": resourceAwsS3ControlBucket(),
"aws_security_group": resourceAwsSecurityGroup(),
"aws_network_interface_sg_attachment": resourceAwsNetworkInterfaceSGAttachment(),
"aws_default_security_group": resourceAwsDefaultSecurityGroup(),
Expand Down
196 changes: 196 additions & 0 deletions aws/resource_aws_s3control_bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package aws

import (
"fmt"
"log"
"regexp"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/s3control"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags"
)

func resourceAwsS3ControlBucket() *schema.Resource {
return &schema.Resource{
Create: resourceAwsS3ControlBucketCreate,
Read: resourceAwsS3ControlBucketRead,
Update: resourceAwsS3ControlBucketUpdate,
Delete: resourceAwsS3ControlBucketDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"bucket": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.All(
validation.StringLenBetween(3, 63),
validation.StringMatch(regexp.MustCompile(`^[a-z0-9.-]+$`), "must contain only lowercase letters, numbers, periods, and hyphens"),
validation.StringMatch(regexp.MustCompile(`^[a-z0-9]`), "must begin with lowercase letter or number"),
validation.StringMatch(regexp.MustCompile(`[a-z0-9]$`), "must end with lowercase letter or number"),
),
},
"creation_date": {
Type: schema.TypeString,
Computed: true,
},
"outpost_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(1, 64),
},
"public_access_block_enabled": {
Type: schema.TypeBool,
Computed: true,
},
"tags": tagsSchema(),
},
}
}

func resourceAwsS3ControlBucketCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).s3controlconn

bucket := d.Get("bucket").(string)

input := &s3control.CreateBucketInput{
Bucket: aws.String(bucket),
OutpostId: aws.String(d.Get("outpost_id").(string)),
}

output, err := conn.CreateBucket(input)

if err != nil {
return fmt.Errorf("error creating S3 Control Bucket (%s): %w", bucket, err)
}

if output == nil {
return fmt.Errorf("error creating S3 Control Bucket (%s): empty response", bucket)
}

d.SetId(aws.StringValue(output.BucketArn))

if v := d.Get("tags").(map[string]interface{}); len(v) > 0 {
if err := keyvaluetags.S3controlBucketUpdateTags(conn, d.Id(), nil, v); err != nil {
return fmt.Errorf("error adding S3 Control Bucket (%s) tags: %w", d.Id(), err)
}
}

return resourceAwsS3ControlBucketRead(d, meta)
}

func resourceAwsS3ControlBucketRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).s3controlconn
ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig

parsedArn, err := arn.Parse(d.Id())

if err != nil {
return fmt.Errorf("error parsing S3 Control Bucket ARN (%s): %w", d.Id(), err)
}

// ARN resource format: outpost/<outpost-id>/bucket/<my-bucket-name>
arnResourceParts := strings.Split(parsedArn.Resource, "/")

if parsedArn.AccountID == "" || len(arnResourceParts) != 4 {
return fmt.Errorf("error parsing S3 Control Bucket ARN (%s): unknown format", d.Id())
}

input := &s3control.GetBucketInput{
AccountId: aws.String(parsedArn.AccountID),
Bucket: aws.String(d.Id()),
}

output, err := conn.GetBucket(input)

if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, "NoSuchBucket") {
log.Printf("[WARN] S3 Control Bucket (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error reading S3 Control Bucket (%s): %w", d.Id(), err)
}

if output == nil {
return fmt.Errorf("error reading S3 Control Bucket (%s): empty response", d.Id())
}

d.Set("arn", d.Id())
d.Set("bucket", output.Bucket)

if output.CreationDate != nil {
d.Set("creation_date", aws.TimeValue(output.CreationDate).Format(time.RFC3339))
}

d.Set("outpost_id", arnResourceParts[1])
d.Set("public_access_block_enabled", output.PublicAccessBlockEnabled)

tags, err := keyvaluetags.S3controlBucketListTags(conn, d.Id())

if err != nil {
return fmt.Errorf("error listing tags for S3 Control Bucket (%s): %w", d.Id(), err)
}

if err := d.Set("tags", tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil {
return fmt.Errorf("error setting tags: %s", err)
}

return nil
}

func resourceAwsS3ControlBucketUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).s3controlconn

if d.HasChange("tags") {
o, n := d.GetChange("tags")

if err := keyvaluetags.S3controlBucketUpdateTags(conn, d.Id(), o, n); err != nil {
return fmt.Errorf("error updating S3 Control Bucket (%s) tags: %w", d.Id(), err)
}
}

return resourceAwsS3ControlBucketRead(d, meta)
}

func resourceAwsS3ControlBucketDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).s3controlconn

parsedArn, err := arn.Parse(d.Id())

if err != nil {
return fmt.Errorf("error parsing S3 Control Bucket ARN (%s): %w", d.Id(), err)
}

input := &s3control.DeleteBucketInput{
AccountId: aws.String(parsedArn.AccountID),
Bucket: aws.String(d.Id()),
}

_, err = conn.DeleteBucket(input)

if tfawserr.ErrCodeEquals(err, "NoSuchBucket") {
return nil
}

if err != nil {
return fmt.Errorf("error deleting S3 Control Bucket (%s): %w", d.Id(), err)
}

return nil
}
Loading

0 comments on commit 4cdf445

Please sign in to comment.