Skip to content

Commit

Permalink
Merge branch 'slapula-resource-aws-client-vpn-network-association'
Browse files Browse the repository at this point in the history
  • Loading branch information
bflad committed Feb 8, 2019
2 parents 8aaa58b + 4fe7408 commit 687570d
Show file tree
Hide file tree
Showing 5 changed files with 419 additions and 0 deletions.
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ func Provider() terraform.ResourceProvider {
"aws_ebs_volume": resourceAwsEbsVolume(),
"aws_ec2_capacity_reservation": resourceAwsEc2CapacityReservation(),
"aws_ec2_client_vpn_endpoint": resourceAwsEc2ClientVpnEndpoint(),
"aws_ec2_client_vpn_network_association": resourceAwsEc2ClientVpnNetworkAssociation(),
"aws_ec2_fleet": resourceAwsEc2Fleet(),
"aws_ec2_transit_gateway": resourceAwsEc2TransitGateway(),
"aws_ec2_transit_gateway_route": resourceAwsEc2TransitGatewayRoute(),
Expand Down
175 changes: 175 additions & 0 deletions aws/resource_aws_ec2_client_vpn_network_association.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package aws

import (
"fmt"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsEc2ClientVpnNetworkAssociation() *schema.Resource {
return &schema.Resource{
Create: resourceAwsEc2ClientVpnNetworkAssociationCreate,
Read: resourceAwsEc2ClientVpnNetworkAssociationRead,
Delete: resourceAwsEc2ClientVpnNetworkAssociationDelete,

Schema: map[string]*schema.Schema{
"client_vpn_endpoint_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"subnet_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"security_groups": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
},
"status": {
Type: schema.TypeString,
Computed: true,
},
"vpc_id": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

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

req := &ec2.AssociateClientVpnTargetNetworkInput{
ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)),
SubnetId: aws.String(d.Get("subnet_id").(string)),
}

log.Printf("[DEBUG] Creating Client VPN network association: %#v", req)
resp, err := conn.AssociateClientVpnTargetNetwork(req)
if err != nil {
return fmt.Errorf("Error creating Client VPN network association: %s", err)
}

d.SetId(*resp.AssociationId)

stateConf := &resource.StateChangeConf{
Pending: []string{ec2.AssociationStatusCodeAssociating},
Target: []string{ec2.AssociationStatusCodeAssociated},
Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)),
Timeout: d.Timeout(schema.TimeoutCreate),
}

log.Printf("[DEBUG] Waiting for Client VPN endpoint to associate with target network: %s", d.Id())
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for Client VPN endpoint to associate with target network: %s", err)
}

return resourceAwsEc2ClientVpnNetworkAssociationRead(d, meta)
}

func resourceAwsEc2ClientVpnNetworkAssociationRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ec2conn
var err error

result, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{
ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)),
AssociationIds: []*string{aws.String(d.Id())},
})

if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") {
log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("Error reading Client VPN network association: %s", err)
}

if result == nil || len(result.ClientVpnTargetNetworks) == 0 || result.ClientVpnTargetNetworks[0] == nil {
log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if result.ClientVpnTargetNetworks[0].Status != nil && aws.StringValue(result.ClientVpnTargetNetworks[0].Status.Code) == ec2.AssociationStatusCodeDisassociated {
log.Printf("[WARN] EC2 Client VPN Network Association (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

d.Set("client_vpn_endpoint_id", result.ClientVpnTargetNetworks[0].ClientVpnEndpointId)
d.Set("status", result.ClientVpnTargetNetworks[0].Status.Code)
d.Set("subnet_id", result.ClientVpnTargetNetworks[0].TargetNetworkId)
d.Set("vpc_id", result.ClientVpnTargetNetworks[0].VpcId)

if err := d.Set("security_groups", aws.StringValueSlice(result.ClientVpnTargetNetworks[0].SecurityGroups)); err != nil {
return fmt.Errorf("error setting security_groups: %s", err)
}

return nil
}

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

_, err := conn.DisassociateClientVpnTargetNetwork(&ec2.DisassociateClientVpnTargetNetworkInput{
ClientVpnEndpointId: aws.String(d.Get("client_vpn_endpoint_id").(string)),
AssociationId: aws.String(d.Id()),
})

if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") {
return nil
}

if err != nil {
return fmt.Errorf("Error deleting Client VPN network association: %s", err)
}

stateConf := &resource.StateChangeConf{
Pending: []string{ec2.AssociationStatusCodeDisassociating},
Target: []string{ec2.AssociationStatusCodeDisassociated},
Refresh: clientVpnNetworkAssociationRefreshFunc(conn, d.Id(), d.Get("client_vpn_endpoint_id").(string)),
Timeout: d.Timeout(schema.TimeoutDelete),
}

log.Printf("[DEBUG] Waiting for Client VPN endpoint to disassociate with target network: %s", d.Id())
_, err = stateConf.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for Client VPN endpoint to disassociate with target network: %s", err)
}

return nil
}

func clientVpnNetworkAssociationRefreshFunc(conn *ec2.EC2, cvnaID string, cvepID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{
ClientVpnEndpointId: aws.String(cvepID),
AssociationIds: []*string{aws.String(cvnaID)},
})

if isAWSErr(err, "InvalidClientVpnAssociationId.NotFound", "") || isAWSErr(err, "InvalidClientVpnEndpointId.NotFound", "") {
return 42, ec2.AssociationStatusCodeDisassociated, nil
}

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

if resp == nil || len(resp.ClientVpnTargetNetworks) == 0 || resp.ClientVpnTargetNetworks[0] == nil {
return 42, ec2.AssociationStatusCodeDisassociated, nil
}

return resp.ClientVpnTargetNetworks[0], aws.StringValue(resp.ClientVpnTargetNetworks[0].Status.Code), nil
}
}
202 changes: 202 additions & 0 deletions aws/resource_aws_ec2_client_vpn_network_association_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package aws

import (
"fmt"
"testing"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAwsEc2ClientVpnNetworkAssociation_basic(t *testing.T) {
var assoc1 ec2.TargetNetwork
rStr := acctest.RandString(5)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProvidersWithTLS,
CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1),
),
},
},
})
}

func TestAccAwsEc2ClientVpnNetworkAssociation_disappears(t *testing.T) {
var assoc1 ec2.TargetNetwork
rStr := acctest.RandString(5)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProvidersWithTLS,
CheckDestroy: testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccEc2ClientVpnNetworkAssociationConfig(rStr),
Check: resource.ComposeTestCheckFunc(
testAccCheckAwsEc2ClientVpnNetworkAssociationExists("aws_ec2_client_vpn_network_association.test", &assoc1),
testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(&assoc1),
),
ExpectNonEmptyPlan: true,
},
},
})
}

func testAccCheckAwsEc2ClientVpnNetworkAssociationDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_ec2_client_vpn_network_association" {
continue
}

resp, _ := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{
ClientVpnEndpointId: aws.String(rs.Primary.Attributes["client_vpn_endpoint_id"]),
AssociationIds: []*string{aws.String(rs.Primary.ID)},
})

for _, v := range resp.ClientVpnTargetNetworks {
if *v.AssociationId == rs.Primary.ID && !(*v.Status.Code == "Disassociated") {
return fmt.Errorf("[DESTROY ERROR] Client VPN network association (%s) not deleted", rs.Primary.ID)
}
}
}

return nil
}

func testAccCheckAwsEc2ClientVpnNetworkAssociationDisappears(targetNetwork *ec2.TargetNetwork) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).ec2conn

_, err := conn.DisassociateClientVpnTargetNetwork(&ec2.DisassociateClientVpnTargetNetworkInput{
AssociationId: targetNetwork.AssociationId,
ClientVpnEndpointId: targetNetwork.ClientVpnEndpointId,
})

if err != nil {
return err
}

stateConf := &resource.StateChangeConf{
Pending: []string{ec2.AssociationStatusCodeDisassociating},
Target: []string{ec2.AssociationStatusCodeDisassociated},
Refresh: clientVpnNetworkAssociationRefreshFunc(conn, aws.StringValue(targetNetwork.AssociationId), aws.StringValue(targetNetwork.ClientVpnEndpointId)),
Timeout: 10 * time.Minute,
}

_, err = stateConf.WaitForState()

return err
}
}

func testAccCheckAwsEc2ClientVpnNetworkAssociationExists(name string, assoc *ec2.TargetNetwork) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}

conn := testAccProvider.Meta().(*AWSClient).ec2conn

resp, err := conn.DescribeClientVpnTargetNetworks(&ec2.DescribeClientVpnTargetNetworksInput{
ClientVpnEndpointId: aws.String(rs.Primary.Attributes["client_vpn_endpoint_id"]),
AssociationIds: []*string{aws.String(rs.Primary.ID)},
})

if err != nil {
return fmt.Errorf("Error reading Client VPN network association (%s): %s", rs.Primary.ID, err)
}

for _, a := range resp.ClientVpnTargetNetworks {
if *a.AssociationId == rs.Primary.ID && !(*a.Status.Code == "Disassociated") {
*assoc = *a
return nil
}
}

return fmt.Errorf("Client VPN network association (%s) not found", rs.Primary.ID)
}
}

func testAccEc2ClientVpnNetworkAssociationConfig(rName string) string {
return fmt.Sprintf(`
resource "aws_vpc" "test" {
cidr_block = "10.1.0.0/16"
tags = {
Name = "terraform-testacc-subnet-%s"
}
}
resource "aws_subnet" "test" {
cidr_block = "10.1.1.0/24"
vpc_id = "${aws_vpc.test.id}"
map_public_ip_on_launch = true
tags = {
Name = "tf-acc-subnet-%s"
}
}
resource "tls_private_key" "example" {
algorithm = "RSA"
}
resource "tls_self_signed_cert" "example" {
key_algorithm = "RSA"
private_key_pem = "${tls_private_key.example.private_key_pem}"
subject {
common_name = "example.com"
organization = "ACME Examples, Inc"
}
validity_period_hours = 12
allowed_uses = [
"key_encipherment",
"digital_signature",
"server_auth",
]
}
resource "aws_acm_certificate" "cert" {
private_key = "${tls_private_key.example.private_key_pem}"
certificate_body = "${tls_self_signed_cert.example.cert_pem}"
}
resource "aws_ec2_client_vpn_endpoint" "test" {
description = "terraform-testacc-clientvpn-%s"
server_certificate_arn = "${aws_acm_certificate.cert.arn}"
client_cidr_block = "10.0.0.0/16"
authentication_options {
type = "certificate-authentication"
root_certificate_chain_arn = "${aws_acm_certificate.cert.arn}"
}
connection_log_options {
enabled = false
}
}
resource "aws_ec2_client_vpn_network_association" "test" {
client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.test.id}"
subnet_id = "${aws_subnet.test.id}"
}
`, rName, rName, rName)
}
4 changes: 4 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,10 @@
<a href="/docs/providers/aws/r/ec2_client_vpn_endpoint.html">aws_ec2_client_vpn_endpoint</a>
</li>

<li<%= sidebar_current("docs-aws-resource-ec2-client-vpn-network-association") %>>
<a href="/docs/providers/aws/r/ec2_client_vpn_network_association.html">aws_ec2_client_vpn_network_association</a>
</li>

<li<%= sidebar_current("docs-aws-resource-ec2-fleet") %>>
<a href="/docs/providers/aws/r/ec2_fleet.html">aws_ec2_fleet</a>
</li>
Expand Down
Loading

0 comments on commit 687570d

Please sign in to comment.