Skip to content

Commit

Permalink
New Resource: aws_s3outposts_endpoint
Browse files Browse the repository at this point in the history
Reference: #15412
Reference: #15417

Output from acceptance testing:

```
--- PASS: TestAccAWSS3OutpostsEndpoint_basic (193.07s)
--- PASS: TestAccAWSS3OutpostsEndpoint_disappears (193.29s)
```
  • Loading branch information
bflad committed Oct 12, 2020
1 parent b1867c4 commit c8cc7d2
Show file tree
Hide file tree
Showing 7 changed files with 513 additions and 0 deletions.
29 changes: 29 additions & 0 deletions aws/internal/service/s3outposts/finder/finder.go
@@ -0,0 +1,29 @@
package finder

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3outposts"
)

// Endpoint returns matching Endpoint by ARN.
func Endpoint(conn *s3outposts.S3Outposts, endpointArn string) (*s3outposts.Endpoint, error) {
input := &s3outposts.ListEndpointsInput{}
var result *s3outposts.Endpoint

err := conn.ListEndpointsPages(input, func(page *s3outposts.ListEndpointsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, endpoint := range page.Endpoints {
if aws.StringValue(endpoint.EndpointArn) == endpointArn {
result = endpoint
return false
}
}

return !lastPage
})

return result, err
}
30 changes: 30 additions & 0 deletions aws/internal/service/s3outposts/waiter/status.go
@@ -0,0 +1,30 @@
package waiter

import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3outposts"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/s3outposts/finder"
)

const (
EndpointStatusNotFound = "NotFound"
EndpointStatusUnknown = "Unknown"
)

// EndpointStatus fetches the Endpoint and its Status
func EndpointStatus(conn *s3outposts.S3Outposts, endpointArn string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
endpoint, err := finder.Endpoint(conn, endpointArn)

if err != nil {
return nil, EndpointStatusUnknown, err
}

if endpoint == nil {
return nil, EndpointStatusNotFound, nil
}

return endpoint, aws.StringValue(endpoint.Status), nil
}
}
37 changes: 37 additions & 0 deletions aws/internal/service/s3outposts/waiter/waiter.go
@@ -0,0 +1,37 @@
package waiter

import (
"time"

"github.com/aws/aws-sdk-go/service/s3outposts"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

const (
// API model constant is incorrectly AVAILABLE
EndpointStatusAvailable = "Available"

// API model constant is incorrectly PENDING
EndpointStatusPending = "Pending"

// Maximum amount of time to wait for Endpoint to return Available on creation
EndpointStatusCreatedTimeout = 5 * time.Minute
)

// EndpointStatusCreated waits for Endpoint to return Available
func EndpointStatusCreated(conn *s3outposts.S3Outposts, endpointArn string) (*s3outposts.Endpoint, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{EndpointStatusPending, EndpointStatusNotFound},
Target: []string{EndpointStatusAvailable},
Refresh: EndpointStatus(conn, endpointArn),
Timeout: EndpointStatusCreatedTimeout,
}

outputRaw, err := stateConf.WaitForState()

if v, ok := outputRaw.(*s3outposts.Endpoint); ok {
return v, err
}

return nil, err
}
1 change: 1 addition & 0 deletions aws/provider.go
Expand Up @@ -855,6 +855,7 @@ func Provider() *schema.Provider {
"aws_s3_bucket_notification": resourceAwsS3BucketNotification(),
"aws_s3_bucket_metric": resourceAwsS3BucketMetric(),
"aws_s3_bucket_inventory": resourceAwsS3BucketInventory(),
"aws_s3outposts_endpoint": resourceAwsS3OutpostsEndpoint(),
"aws_security_group": resourceAwsSecurityGroup(),
"aws_network_interface_sg_attachment": resourceAwsNetworkInterfaceSGAttachment(),
"aws_default_security_group": resourceAwsDefaultSecurityGroup(),
Expand Down
212 changes: 212 additions & 0 deletions aws/resource_aws_s3outposts_endpoint.go
@@ -0,0 +1,212 @@
package aws

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

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/s3outposts"
"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/service/s3outposts/finder"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/s3outposts/waiter"
)

func resourceAwsS3OutpostsEndpoint() *schema.Resource {
return &schema.Resource{
Create: resourceAwsS3OutpostsEndpointCreate,
Read: resourceAwsS3OutpostsEndpointRead,
Delete: resourceAwsS3OutpostsEndpointDelete,

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

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"cidr_block": {
Type: schema.TypeString,
Computed: true,
},
"creation_time": {
Type: schema.TypeString,
Computed: true,
},
"network_interfaces": {
Type: schema.TypeSet,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"network_interface_id": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
"outpost_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},
"security_group_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},
"subnet_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringIsNotEmpty,
},
},
}
}

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

input := &s3outposts.CreateEndpointInput{
OutpostId: aws.String(d.Get("outpost_id").(string)),
SecurityGroupId: aws.String(d.Get("security_group_id").(string)),
SubnetId: aws.String(d.Get("subnet_id").(string)),
}

output, err := conn.CreateEndpoint(input)

if err != nil {
return fmt.Errorf("error creating S3 Outposts Endpoint: %w", err)
}

if output == nil {
return fmt.Errorf("error creating S3 Outposts Endpoint: empty response")
}

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

if _, err := waiter.EndpointStatusCreated(conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for S3 Outposts Endpoint (%s) to become available: %w", d.Id(), err)
}

return resourceAwsS3OutpostsEndpointRead(d, meta)
}

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

endpoint, err := finder.Endpoint(conn, d.Id())

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

if endpoint == nil {
if d.IsNewResource() {
return fmt.Errorf("error reading S3 Outposts Endpoint (%s): not found after creation", d.Id())
}

log.Printf("[WARN] S3 Outposts Endpoint (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

d.Set("arn", endpoint.EndpointArn)
d.Set("cidr_block", endpoint.CidrBlock)

if endpoint.CreationTime != nil {
d.Set("creation_time", aws.TimeValue(endpoint.CreationTime).Format(time.RFC3339))
}

if err := d.Set("network_interfaces", flattenS3outpostsNetworkInterfaces(endpoint.NetworkInterfaces)); err != nil {
return fmt.Errorf("error setting network_interfaces: %w", err)
}

d.Set("outpost_id", endpoint.OutpostsId)

return nil
}

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

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

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

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

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

input := &s3outposts.DeleteEndpointInput{
EndpointId: aws.String(arnResourceParts[3]),
OutpostId: aws.String(arnResourceParts[1]),
}

_, err = conn.DeleteEndpoint(input)

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

return nil
}

func resourceAwsS3OutpostsEndpointImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
idParts := strings.Split(d.Id(), ",")

if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
return nil, fmt.Errorf("unexpected format of ID (%s), expected ENDPOINT-ARN,SECURITY-GROUP-ID,SUBNET-ID", d.Id())
}

endpointArn := idParts[0]
securityGroupId := idParts[1]
subnetId := idParts[2]

d.SetId(endpointArn)
d.Set("security_group_id", securityGroupId)
d.Set("subnet_id", subnetId)

return []*schema.ResourceData{d}, nil
}

func flattenS3outpostsNetworkInterfaces(apiObjects []*s3outposts.NetworkInterface) []interface{} {
var tfList []interface{}

for _, apiObject := range apiObjects {
if apiObject == nil {
continue
}

tfList = append(tfList, flattenS3outpostsNetworkInterface(apiObject))
}

return tfList
}

func flattenS3outpostsNetworkInterface(apiObject *s3outposts.NetworkInterface) map[string]interface{} {
if apiObject == nil {
return nil
}

tfMap := map[string]interface{}{}

if v := apiObject.NetworkInterfaceId; v != nil {
tfMap["network_interface_id"] = aws.StringValue(v)
}

return tfMap
}

0 comments on commit c8cc7d2

Please sign in to comment.