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

feat: add aws_dx_hosted_connection and aws_dx_connection_confirmation resource #16489

Merged
merged 9 commits into from
Oct 6, 2021
11 changes: 11 additions & 0 deletions .changelog/16489.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:new-resource
aws_dx_hosted_connection
```

```release-note:new-resource
aws_dx_connection_confirmation
```

```release-note:enhancement
resource/aws_dx_lag: Add `connection_id` argument
```
38 changes: 38 additions & 0 deletions aws/internal/service/directconnect/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,44 @@ func GatewayAssociationProposalByID(conn *directconnect.DirectConnect, id string
return proposal, nil
}

func HostedConnectionByID(conn *directconnect.DirectConnect, id string) (*directconnect.Connection, error) {
input := &directconnect.DescribeHostedConnectionsInput{
ConnectionId: aws.String(id),
}

output, err := conn.DescribeHostedConnections(input)

if tfawserr.ErrMessageContains(err, directconnect.ErrCodeClientException, "Could not find Connection with ID") {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

if output == nil || len(output.Connections) == 0 || output.Connections[0] == nil {
return nil, tfresource.NewEmptyResultError(input)
}

if count := len(output.Connections); count > 1 {
return nil, tfresource.NewTooManyResultsError(count, input)
}

connection := output.Connections[0]

if state := aws.StringValue(connection.ConnectionState); state == directconnect.ConnectionStateDeleted || state == directconnect.ConnectionStateRejected {
return nil, &resource.NotFoundError{
Message: state,
LastRequest: input,
}
}

return connection, nil
}

func LagByID(conn *directconnect.DirectConnect, id string) (*directconnect.Lag, error) {
input := &directconnect.DescribeLagsInput{
LagId: aws.String(id),
Expand Down
16 changes: 16 additions & 0 deletions aws/internal/service/directconnect/waiter/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,22 @@ func GatewayAssociationState(conn *directconnect.DirectConnect, id string) resou
}
}

func HostedConnectionState(conn *directconnect.DirectConnect, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := finder.HostedConnectionByID(conn, id)

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

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

return output, aws.StringValue(output.ConnectionState), nil
}
}

func LagState(conn *directconnect.DirectConnect, id string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := finder.LagByID(conn, id)
Expand Down
36 changes: 36 additions & 0 deletions aws/internal/service/directconnect/waiter/waiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,30 @@ import (
)

const (
ConnectionConfirmedTimeout = 10 * time.Minute
ConnectionDeletedTimeout = 10 * time.Minute
ConnectionDisassociatedTimeout = 1 * time.Minute
HostedConnectionDeletedTimeout = 10 * time.Minute
LagDeletedTimeout = 10 * time.Minute
)

func ConnectionConfirmed(conn *directconnect.DirectConnect, id string) (*directconnect.Connection, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{directconnect.ConnectionStatePending, directconnect.ConnectionStateOrdering, directconnect.ConnectionStateRequested},
Target: []string{directconnect.ConnectionStateAvailable},
Refresh: ConnectionState(conn, id),
Timeout: ConnectionConfirmedTimeout,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*directconnect.Connection); ok {
return output, err
}

return nil, err
}

func ConnectionDeleted(conn *directconnect.DirectConnect, id string) (*directconnect.Connection, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{directconnect.ConnectionStatePending, directconnect.ConnectionStateOrdering, directconnect.ConnectionStateAvailable, directconnect.ConnectionStateRequested, directconnect.ConnectionStateDeleting},
Expand Down Expand Up @@ -128,6 +147,23 @@ func GatewayAssociationDeleted(conn *directconnect.DirectConnect, id string, tim
return nil, err
}

func HostedConnectionDeleted(conn *directconnect.DirectConnect, id string) (*directconnect.Connection, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{directconnect.ConnectionStatePending, directconnect.ConnectionStateOrdering, directconnect.ConnectionStateAvailable, directconnect.ConnectionStateRequested, directconnect.ConnectionStateDeleting},
Target: []string{},
Refresh: HostedConnectionState(conn, id),
Timeout: HostedConnectionDeletedTimeout,
}

outputRaw, err := stateConf.WaitForState()

if output, ok := outputRaw.(*directconnect.Connection); ok {
return output, err
}

return nil, err
}

func LagDeleted(conn *directconnect.DirectConnect, id string) (*directconnect.Lag, error) {
stateConf := &resource.StateChangeConf{
Pending: []string{directconnect.LagStateAvailable, directconnect.LagStateRequested, directconnect.LagStatePending, directconnect.LagStateDeleting},
Expand Down
2 changes: 2 additions & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,9 +707,11 @@ func Provider() *schema.Provider {
"aws_dx_bgp_peer": resourceAwsDxBgpPeer(),
"aws_dx_connection": resourceAwsDxConnection(),
"aws_dx_connection_association": resourceAwsDxConnectionAssociation(),
"aws_dx_connection_confirmation": resourceAwsDxConnectionConfirmation(),
"aws_dx_gateway": resourceAwsDxGateway(),
"aws_dx_gateway_association": resourceAwsDxGatewayAssociation(),
"aws_dx_gateway_association_proposal": resourceAwsDxGatewayAssociationProposal(),
"aws_dx_hosted_connection": resourceAwsDxHostedConnection(),
"aws_dx_hosted_private_virtual_interface": resourceAwsDxHostedPrivateVirtualInterface(),
"aws_dx_hosted_private_virtual_interface_accepter": resourceAwsDxHostedPrivateVirtualInterfaceAccepter(),
"aws_dx_hosted_public_virtual_interface": resourceAwsDxHostedPublicVirtualInterface(),
Expand Down
6 changes: 3 additions & 3 deletions aws/resource_aws_dx_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,10 @@ func resourceAwsDxConnectionUpdate(d *schema.ResourceData, meta interface{}) err
func resourceAwsDxConnectionDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dxconn

return deleteDirectConnectConnection(conn, d.Id())
return deleteDirectConnectConnection(conn, d.Id(), waiter.ConnectionDeleted)
}

func deleteDirectConnectConnection(conn *directconnect.DirectConnect, connectionID string) error {
func deleteDirectConnectConnection(conn *directconnect.DirectConnect, connectionID string, waiter func(*directconnect.DirectConnect, string) (*directconnect.Connection, error)) error {
log.Printf("[DEBUG] Deleting Direct Connect Connection: %s", connectionID)
_, err := conn.DeleteConnection(&directconnect.DeleteConnectionInput{
ConnectionId: aws.String(connectionID),
Expand All @@ -197,7 +197,7 @@ func deleteDirectConnectConnection(conn *directconnect.DirectConnect, connection
return fmt.Errorf("error deleting Direct Connect Connection (%s): %w", connectionID, err)
}

_, err = waiter.ConnectionDeleted(conn, connectionID)
_, err = waiter(conn, connectionID)

if err != nil {
return fmt.Errorf("error waiting for Direct Connect Connection (%s) delete: %w", connectionID, err)
Expand Down
9 changes: 6 additions & 3 deletions aws/resource_aws_dx_connection_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ func resourceAwsDxConnectionAssociationRead(d *schema.ResourceData, meta interfa
func resourceAwsDxConnectionAssociationDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).dxconn

lagID := d.Get("lag_id").(string)
return deleteDirectConnectConnectionLAGAssociation(conn, d.Id(), d.Get("lag_id").(string))
}

func deleteDirectConnectConnectionLAGAssociation(conn *directconnect.DirectConnect, connectionID, lagID string) error {
input := &directconnect.DisassociateConnectionFromLagInput{
ConnectionId: aws.String(d.Id()),
ConnectionId: aws.String(connectionID),
LagId: aws.String(lagID),
}

Expand All @@ -104,7 +107,7 @@ func resourceAwsDxConnectionAssociationDelete(d *schema.ResourceData, meta inter
)

if err != nil {
return fmt.Errorf("error deleting Direct Connect Connection (%s) LAG (%s) Association: %w", d.Id(), lagID, err)
return fmt.Errorf("error deleting Direct Connect Connection (%s) LAG (%s) Association: %w", connectionID, lagID, err)
}

return err
Expand Down
69 changes: 61 additions & 8 deletions aws/resource_aws_dx_connection_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
)

func TestAccAWSDxConnectionAssociation_basic(t *testing.T) {
resourceName := "aws_dx_connection_association.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
Expand All @@ -22,16 +23,17 @@ func TestAccAWSDxConnectionAssociation_basic(t *testing.T) {
CheckDestroy: testAccCheckAwsDxConnectionAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccDxConnectionAssociationConfig(rName),
Config: testAccDxConnectionAssociationConfigBasic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsDxConnectionAssociationExists("aws_dx_connection_association.test"),
testAccCheckAwsDxConnectionAssociationExists(resourceName),
),
},
},
})
}

func TestAccAWSDxConnectionAssociation_multiConns(t *testing.T) {
func TestAccAWSDxConnectionAssociation_LAGOnConnection(t *testing.T) {
resourceName := "aws_dx_connection_association.test"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
Expand All @@ -41,10 +43,31 @@ func TestAccAWSDxConnectionAssociation_multiConns(t *testing.T) {
CheckDestroy: testAccCheckAwsDxConnectionAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccDxConnectionAssociationConfig_multiConns(rName),
Config: testAccDxConnectionAssociationConfigLAGOnConnection(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsDxConnectionAssociationExists("aws_dx_connection_association.test1"),
testAccCheckAwsDxConnectionAssociationExists("aws_dx_connection_association.test2"),
testAccCheckAwsDxConnectionAssociationExists(resourceName),
),
},
},
})
}

func TestAccAWSDxConnectionAssociation_Multiple(t *testing.T) {
resourceName1 := "aws_dx_connection_association.test1"
resourceName2 := "aws_dx_connection_association.test2"
rName := acctest.RandomWithPrefix("tf-acc-test")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ErrorCheck: testAccErrorCheck(t, directconnect.EndpointsID),
Providers: testAccProviders,
CheckDestroy: testAccCheckAwsDxConnectionAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccDxConnectionAssociationConfigMultiple(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsDxConnectionAssociationExists(resourceName1),
testAccCheckAwsDxConnectionAssociationExists(resourceName2),
),
},
},
Expand Down Expand Up @@ -98,7 +121,7 @@ func testAccCheckAwsDxConnectionAssociationExists(name string) resource.TestChec
}
}

func testAccDxConnectionAssociationConfig(rName string) string {
func testAccDxConnectionAssociationConfigBasic(rName string) string {
return fmt.Sprintf(`
data "aws_dx_locations" "test" {}

Expand All @@ -122,7 +145,37 @@ resource "aws_dx_connection_association" "test" {
`, rName)
}

func testAccDxConnectionAssociationConfig_multiConns(rName string) string {
func testAccDxConnectionAssociationConfigLAGOnConnection(rName string) string {
return fmt.Sprintf(`
data "aws_dx_locations" "test" {}

resource "aws_dx_connection" "test1" {
name = "%[1]s-1"
bandwidth = "1Gbps"
location = tolist(data.aws_dx_locations.test.location_codes)[0]
}

resource "aws_dx_connection" "test2" {
name = "%[1]s-2"
bandwidth = "1Gbps"
location = tolist(data.aws_dx_locations.test.location_codes)[0]
}

resource "aws_dx_lag" "test" {
name = %[1]q
connection_id = aws_dx_connection.test1.id
connections_bandwidth = "1Gbps"
location = tolist(data.aws_dx_locations.test.location_codes)[0]
}

resource "aws_dx_connection_association" "test" {
connection_id = aws_dx_connection.test2.id
lag_id = aws_dx_lag.test.id
}
`, rName)
}

func testAccDxConnectionAssociationConfigMultiple(rName string) string {
return fmt.Sprintf(`
data "aws_dx_locations" "test" {}

Expand Down
76 changes: 76 additions & 0 deletions aws/resource_aws_dx_connection_confirmation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package aws

import (
"fmt"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/directconnect"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/finder"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/directconnect/waiter"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsDxConnectionConfirmation() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDxConnectionConfirmationCreate,
Read: resourceAwsDxConnectionConfirmationRead,
Delete: resourceAwsDxConnectionConfirmationDelete,

Schema: map[string]*schema.Schema{
"connection_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

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

connectionID := d.Get("connection_id").(string)
input := &directconnect.ConfirmConnectionInput{
ConnectionId: aws.String(connectionID),
}

log.Printf("[DEBUG] Confirming Direct Connect Connection: %s", input)
_, err := conn.ConfirmConnection(input)

if err != nil {
return fmt.Errorf("error confirming Direct Connection Connection (%s): %w", connectionID, err)
}

d.SetId(connectionID)

if _, err := waiter.ConnectionConfirmed(conn, d.Id()); err != nil {
return fmt.Errorf("error waiting for Direct Connection Connection (%s) confirm: %w", d.Id(), err)
}

return nil
}

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

_, err := finder.ConnectionByID(conn, d.Id())

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] Direct Connect Connection (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error reading Direct Connect Connection (%s): %w", d.Id(), err)
}

return nil
}

func resourceAwsDxConnectionConfirmationDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf("[WARN] Will not delete Direct Connect connection. Terraform will remove this resource from the state file, however resources may remain.")
return nil
}
Loading