-
Notifications
You must be signed in to change notification settings - Fork 9.2k
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
r/aws_ec2_client_vpn_network_association: Adding resource to manage Client VPN network associations #7030
Merged
bflad
merged 9 commits into
hashicorp:master
from
slapula:resource-aws-client-vpn-network-association
Feb 8, 2019
Merged
r/aws_ec2_client_vpn_network_association: Adding resource to manage Client VPN network associations #7030
Changes from 5 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
1a20855
initial commit
slapula b199c4c
fixing merge conflict
slapula fc1ff56
r/aws_ec2_client_vpn_network_association: Adding resource to manage C…
slapula c6c3c36
adding doc to website
slapula 370d78d
fixing typo
slapula 6b9a661
Merge branch 'master' into resource-aws-client-vpn-network-association
slapula cf6d047
more changes based on review feedback
slapula 9e31e5e
more review changes
slapula 5d587d8
removing bool constant
slapula File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"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, | ||
Importer: &schema.ResourceImporter{ | ||
State: schema.ImportStatePassthrough, | ||
}, | ||
|
||
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, | ||
}, | ||
}, | ||
|
||
Timeouts: &schema.ResourceTimeout{ | ||
slapula marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Create: schema.DefaultTimeout(10 * time.Minute), | ||
Delete: schema.DefaultTimeout(10 * time.Minute), | ||
}, | ||
} | ||
} | ||
|
||
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{"associating"}, | ||
Target: []string{"associated"}, | ||
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 err != nil { | ||
return fmt.Errorf("Error reading Client VPN network association: %s", err) | ||
} | ||
|
||
d.Set("client_vpn_endpoint_id", result.ClientVpnTargetNetworks[0].ClientVpnEndpointId) | ||
d.Set("security_groups", result.ClientVpnTargetNetworks[0].SecurityGroups) | ||
slapula marked this conversation as resolved.
Show resolved
Hide resolved
|
||
d.Set("status", result.ClientVpnTargetNetworks[0].Status) | ||
d.Set("subnet_id", result.ClientVpnTargetNetworks[0].TargetNetworkId) | ||
d.Set("vpc_id", result.ClientVpnTargetNetworks[0].VpcId) | ||
|
||
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 err != nil { | ||
return fmt.Errorf("Error deleting Client VPN network association: %s", err) | ||
} | ||
|
||
slapula marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 err != nil { | ||
return nil, "", err | ||
} | ||
|
||
return resp.ClientVpnTargetNetworks[0], aws.StringValue(resp.ClientVpnTargetNetworks[0].Status.Code), nil | ||
} | ||
} |
154 changes: 154 additions & 0 deletions
154
aws/resource_aws_ec2_client_vpn_network_association_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package aws | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"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.Test(t, resource.TestCase{ | ||
slapula marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 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 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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
website/docs/r/ec2_client_vpn_network_association.html.markdown
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
--- | ||
layout: "aws" | ||
page_title: "AWS: aws_ec2_client_vpn_network_association" | ||
sidebar_current: "docs-aws-resource-ec2-client-vpn-network-association" | ||
description: |- | ||
Provides network associations for AWS Client VPN endpoints. | ||
--- | ||
|
||
# aws_ec2_client_vpn_network_association | ||
|
||
Provides network associations for AWS Client VPN endpoints. For more information on usage, please see the | ||
[AWS Client VPN Administrator's Guide](https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/what-is.html). | ||
|
||
## Example Usage | ||
|
||
```hcl | ||
resource "aws_ec2_client_vpn_network_association" "example" { | ||
client_vpn_endpoint_id = "${aws_ec2_client_vpn_endpoint.example.id}" | ||
subnet_id = "${aws_subnet.example.id}" | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
The following arguments are supported: | ||
|
||
* `client_vpn_endpoint_id` - (Required) The ID of the Client VPN endpoint. | ||
* `subnet_id` - (Required) The ID of the subnet to associate with the Client VPN endpoint. | ||
|
||
## Attributes Reference | ||
|
||
In addition to all arguments above, the following attributes are exported: | ||
|
||
* `id` - The unique ID of the target network association. | ||
* `security_groups` - The IDs of the security groups applied to the target network association. | ||
* `status` - The current state of the target network association. | ||
* `vpc_id` - The ID of the VPC in which the target network (subnet) is located. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This import support is missing:
TestStep
that includesImportState: true
andImportStateVerify: true
## Import
section in the resource documentation.The support should be fully implemented or removed from the pull request. 👍
If you are opting to keep the support, you will notice that you will need to either implement a custom import function to that calls
d.Set("client_vpn_endpoint_id", ...)
and trims that part of the ID ford.SetId()
(then useImportStateIdFunc
in acceptance testing) or switch the resource to implementing a "complex" ID that includes both IDs in its string, e.g.ENDPOINT/ASSOCIATION
. For new implementations, we have generally preferred the latter form where we parse the complex ID and continue to useImportStatePassthrough
, but either form is acceptable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this is cruft from my copy/paste when I started working on this. I'm going to opt to not include import functionality here since I wasn't planning on adding it initially. I can be swayed to add it however because I can see the need for it.