Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for updating tags in apprunner vpc connection #27345

Merged
merged 10 commits into from
Oct 24, 2022
Merged
3 changes: 3 additions & 0 deletions .changelog/27345.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_apprunner_vpc_connector: Add ability to update `tags`
```
20 changes: 0 additions & 20 deletions internal/service/apprunner/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,6 @@ func StatusObservabilityConfiguration(ctx context.Context, conn *apprunner.AppRu
}
}

func StatusVPCConnector(ctx context.Context, conn *apprunner.AppRunner, arn string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
input := &apprunner.DescribeVpcConnectorInput{
VpcConnectorArn: aws.String(arn),
}

output, err := conn.DescribeVpcConnectorWithContext(ctx, input)

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

if output == nil || output.VpcConnector == nil {
return nil, "", nil
}

return output.VpcConnector, aws.StringValue(output.VpcConnector.Status), nil
}
}

func StatusConnection(ctx context.Context, conn *apprunner.AppRunner, name string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
c, err := FindConnectionSummaryByName(ctx, conn, name)
Expand Down
221 changes: 146 additions & 75 deletions internal/service/apprunner/vpc_connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,35 @@ package apprunner

import (
"context"
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/apprunner"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
)

func ResourceVPCConnector() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceVPCConnectorCreate,
ReadWithoutTimeout: resourceVPCConnectorRead,
UpdateWithoutTimeout: resourceVPCConnectorUpdate,
DeleteWithoutTimeout: resourceVPCConnectorDelete,

Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"vpc_connector_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(4, 40),
},
"arn": {
Type: schema.TypeString,
Computed: true,
Expand All @@ -42,25 +40,31 @@ func ResourceVPCConnector() *schema.Resource {
Required: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"status": {
Type: schema.TypeString,
Computed: true,
},
"subnets": {
Type: schema.TypeSet,
Required: true,
ForceNew: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"tags": tftags.TagsSchemaComputed(),
"status": {
Type: schema.TypeString,
Computed: true,
"tags": tftags.TagsSchemaComputed(),
"tags_all": tftags.TagsSchemaComputed(),
"vpc_connector_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringLenBetween(4, 40),
},
"vpc_connector_revision": {
Type: schema.TypeInt,
Computed: true,
},
},
CustomizeDiff: verify.SetTagsDiff,
}
}

Expand All @@ -70,13 +74,10 @@ func resourceVPCConnectorCreate(ctx context.Context, d *schema.ResourceData, met
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

vpcConnectorName := d.Get("vpc_connector_name").(string)
subnets := flex.ExpandStringSet(d.Get("subnets").(*schema.Set))
securityGroups := flex.ExpandStringSet(d.Get("security_groups").(*schema.Set))

input := &apprunner.CreateVpcConnectorInput{
SecurityGroups: flex.ExpandStringSet(d.Get("security_groups").(*schema.Set)),
Subnets: flex.ExpandStringSet(d.Get("subnets").(*schema.Set)),
VpcConnectorName: aws.String(vpcConnectorName),
Subnets: subnets,
SecurityGroups: securityGroups,
}

if len(tags) > 0 {
Expand All @@ -86,17 +87,13 @@ func resourceVPCConnectorCreate(ctx context.Context, d *schema.ResourceData, met
output, err := conn.CreateVpcConnectorWithContext(ctx, input)

if err != nil {
return diag.FromErr(fmt.Errorf("error creating App Runner vpc (%s): %w", vpcConnectorName, err))
}

if output == nil {
return diag.FromErr(fmt.Errorf("error creating App Runner vpc (%s): empty output", vpcConnectorName))
return diag.Errorf("creating App Runner VPC Connector (%s): %s", vpcConnectorName, err)
}

d.SetId(aws.StringValue(output.VpcConnector.VpcConnectorArn))

if err := waitVPCConnectorActive(ctx, conn, d.Id()); err != nil {
return diag.FromErr(fmt.Errorf("error waiting for creating App Runner vpc (%s) creation: %w", d.Id(), err))
if err := waitVPCConnectorCreated(ctx, conn, d.Id()); err != nil {
return diag.Errorf("waiting for App Runner VPC Connector (%s) create: %s", d.Id(), err)
}

return resourceVPCConnectorRead(ctx, d, meta)
Expand All @@ -107,93 +104,167 @@ func resourceVPCConnectorRead(ctx context.Context, d *schema.ResourceData, meta
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

input := &apprunner.DescribeVpcConnectorInput{
VpcConnectorArn: aws.String(d.Id()),
}

output, err := conn.DescribeVpcConnectorWithContext(ctx, input)

if err != nil {
return diag.FromErr(fmt.Errorf("error reading App Runner vpc connector (%s): %w", d.Id(), err))
}
vpcConnector, err := FindVPCConnectorByARN(ctx, conn, d.Id())

if output == nil || output.VpcConnector == nil {
return diag.FromErr(fmt.Errorf("error reading App Runner vpc connector (%s): empty output", d.Id()))
}

if aws.StringValue(output.VpcConnector.Status) == apprunner.VpcConnectorStatusInactive {
if d.IsNewResource() {
return diag.FromErr(fmt.Errorf("error reading App Runner vpc connector (%s): %s after creation", d.Id(), aws.StringValue(output.VpcConnector.Status)))
}
log.Printf("[WARN] App Runner vpc connector (%s) not found, removing from state", d.Id())
if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] App Runner VPC Connector (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

vpcConnector := output.VpcConnector
arn := aws.StringValue(vpcConnector.VpcConnectorArn)
if err != nil {
return diag.Errorf("reading App Runner VPC Connector (%s): %s", d.Id(), err)
}

d.Set("vpc_connector_name", vpcConnector.VpcConnectorName)
d.Set("vpc_connector_revision", vpcConnector.VpcConnectorRevision)
arn := aws.StringValue(vpcConnector.VpcConnectorArn)
d.Set("arn", vpcConnector.VpcConnectorArn)
d.Set("security_groups", aws.StringValueSlice(vpcConnector.SecurityGroups))
d.Set("status", vpcConnector.Status)
d.Set("subnets", aws.StringValueSlice(vpcConnector.Subnets))
d.Set("vpc_connector_name", vpcConnector.VpcConnectorName)
d.Set("vpc_connector_revision", vpcConnector.VpcConnectorRevision)

var subnets []string
for _, sn := range vpcConnector.Subnets {
subnets = append(subnets, aws.StringValue(sn))
}
if err := d.Set("subnets", subnets); err != nil {
return diag.FromErr(fmt.Errorf("Error saving Subnet IDs to state for App Runner vpc connector (%s): %s", d.Id(), err))
}

var securityGroups []string
for _, sn := range vpcConnector.SecurityGroups {
securityGroups = append(securityGroups, aws.StringValue(sn))
}
if err := d.Set("security_groups", securityGroups); err != nil {
return diag.FromErr(fmt.Errorf("Error saving securityGroup IDs to state for App Runner vpc connector (%s): %s", d.Id(), err))
}

tags, err := ListTags(conn, arn)
tags, err := ListTagsWithContext(ctx, conn, arn)

if err != nil {
return diag.Errorf("error listing tags for App Runner vpc connector (%s): %s", d.Id(), err)
return diag.Errorf("listing tags for App Runner VPC Connector (%s): %s", d.Id(), err)
}

tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return diag.FromErr(fmt.Errorf("error setting tags: %w", err))
return diag.Errorf("setting tags: %s", err)
}

if err := d.Set("tags_all", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return diag.Errorf("setting tags_all: %s", err)
}

return nil
}

func resourceVPCConnectorUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).AppRunnerConn

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

if err := UpdateTagsWithContext(ctx, conn, d.Get("arn").(string), o, n); err != nil {
return diag.Errorf("updating App Runner VPC Connector (%s) tags: %s", d.Id(), err)
}
}

return resourceVPCConnectorRead(ctx, d, meta)
}

func resourceVPCConnectorDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).AppRunnerConn

input := &apprunner.DeleteVpcConnectorInput{
log.Printf("[DEBUG] Deleting App Runner VPC Connector: %s", d.Id())
_, err := conn.DeleteVpcConnectorWithContext(ctx, &apprunner.DeleteVpcConnectorInput{
VpcConnectorArn: aws.String(d.Id()),
})

if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) {
return nil
}

if err != nil {
return diag.Errorf("deleting App Runner VPC Connector (%s): %s", d.Id(), err)
}

if err := waitVPCConnectorDeleted(ctx, conn, d.Id()); err != nil {
return diag.Errorf("waiting for App Runner VPC Connector (%s) delete: %s", d.Id(), err)
}

return nil
}

func FindVPCConnectorByARN(ctx context.Context, conn *apprunner.AppRunner, arn string) (*apprunner.VpcConnector, error) {
input := &apprunner.DescribeVpcConnectorInput{
VpcConnectorArn: aws.String(arn),
}

output, err := findVPCConnector(ctx, conn, input)

if err != nil {
return nil, err
}

if status := aws.StringValue(output.Status); status == apprunner.VpcConnectorStatusInactive {
return nil, &resource.NotFoundError{
Message: status,
LastRequest: input,
}
}

_, err := conn.DeleteVpcConnectorWithContext(ctx, input)
return output, nil
}

func findVPCConnector(ctx context.Context, conn *apprunner.AppRunner, input *apprunner.DescribeVpcConnectorInput) (*apprunner.VpcConnector, error) {
output, err := conn.DescribeVpcConnectorWithContext(ctx, input)

if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) {
return nil
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return diag.FromErr(fmt.Errorf("error deleting App Runner vpc connector (%s): %w", d.Id(), err))
return nil, err
}

if err := waitVPCConnectorInactive(ctx, conn, d.Id()); err != nil {
if tfawserr.ErrCodeEquals(err, apprunner.ErrCodeResourceNotFoundException) {
return nil
if output == nil || output.VpcConnector == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output.VpcConnector, nil
}

func statusVPCConnector(ctx context.Context, conn *apprunner.AppRunner, arn string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := FindVPCConnectorByARN(ctx, conn, arn)

if tfresource.NotFound(err) {
return nil, "", nil
}

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

return diag.FromErr(fmt.Errorf("error waiting for App Runner vpc connector (%s) deletion: %w", d.Id(), err))
return output, aws.StringValue(output.Status), nil
}
}

return nil
const (
vpcConnectorCreateTimeout = 2 * time.Minute
vpcConnectorDeleteTimeout = 2 * time.Minute
)

func waitVPCConnectorCreated(ctx context.Context, conn *apprunner.AppRunner, arn string) error {
stateConf := &resource.StateChangeConf{
Target: []string{apprunner.VpcConnectorStatusActive},
Refresh: statusVPCConnector(ctx, conn, arn),
Timeout: vpcConnectorCreateTimeout,
}

_, err := stateConf.WaitForStateContext(ctx)

return err
}

func waitVPCConnectorDeleted(ctx context.Context, conn *apprunner.AppRunner, arn string) error {
stateConf := &resource.StateChangeConf{
Pending: []string{apprunner.VpcConnectorStatusActive},
Target: []string{},
Refresh: statusVPCConnector(ctx, conn, arn),
Timeout: vpcConnectorDeleteTimeout,
}

_, err := stateConf.WaitForStateContext(ctx)

return err
}
Loading