mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-20 11:48:24 -06:00
availability zone Fixes #4752 According to the AWS Documentation, when `describing-vpn-gateways` ``` AvailabilityZone -> (string) The Availability Zone where the virtual private gateway was created, if applicable. This field may be empty or not returned. ``` Therefore, if we pass an availability zone as part of vpn gateway, then it may come back as an empty string. If we set this empty string back to state, then the next plan will look as follows: ``` -/+ aws_vpn_gateway.vpn_gateway availability_zone: "" => "us-west-2a" (forces new resource) tags.%: "1" => "1" tags.Name: "vpn-us-west-2" => "vpn-us-west-2" vpc_id: "vpc-1e9da47a" => "vpc-1e9da47a" Plan: 1 to add, 0 to change, 1 to destroy. ``` If the availability_zone comes back from AWS as an empty string, then we should not set it to state to avoid forcing a new resource for the user ``` % make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSVpnGateway_withAvailabilityZoneSetToState' ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) 2016/09/03 17:10:57 Generated command/internal_plugin_list.go TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSVpnGateway_withAvailabilityZoneSetToState -timeout 120m === RUN TestAccAWSVpnGateway_withAvailabilityZoneSetToState --- FAIL: TestAccAWSVpnGateway_withAvailabilityZoneSetToState (36.11s) testing.go:265: Step 0 error: Check failed: Check 2/2 error: aws_vpn_gateway.foo: Attribute 'availability_zone' expected "us-west-2a", got "" FAIL exit status 1 FAIL github.com/hashicorp/terraform/builtin/providers/aws 36.130s make: *** [testacc] Error 1 [stacko@Pauls-MacBook-Pro:~/Code/go/src/github.com/hashicorp/terraform on master] % make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSVpnGateway_withAvailabilityZoneSetToState' 2 ↵ ✹ ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) 2016/09/03 17:12:25 Generated command/internal_plugin_list.go TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSVpnGateway_withAvailabilityZoneSetToState -timeout 120m === RUN TestAccAWSVpnGateway_withAvailabilityZoneSetToState --- PASS: TestAccAWSVpnGateway_withAvailabilityZoneSetToState (46.50s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws 46.517s ```
327 lines
8.1 KiB
Go
327 lines
8.1 KiB
Go
package aws
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
)
|
|
|
|
func resourceAwsVpnGateway() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceAwsVpnGatewayCreate,
|
|
Read: resourceAwsVpnGatewayRead,
|
|
Update: resourceAwsVpnGatewayUpdate,
|
|
Delete: resourceAwsVpnGatewayDelete,
|
|
Importer: &schema.ResourceImporter{
|
|
State: schema.ImportStatePassthrough,
|
|
},
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"availability_zone": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ForceNew: true,
|
|
},
|
|
|
|
"vpc_id": &schema.Schema{
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Computed: true,
|
|
},
|
|
|
|
"tags": tagsSchema(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
createOpts := &ec2.CreateVpnGatewayInput{
|
|
AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
|
|
Type: aws.String("ipsec.1"),
|
|
}
|
|
|
|
// Create the VPN gateway
|
|
log.Printf("[DEBUG] Creating VPN gateway")
|
|
resp, err := conn.CreateVpnGateway(createOpts)
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating VPN gateway: %s", err)
|
|
}
|
|
|
|
// Get the ID and store it
|
|
vpnGateway := resp.VpnGateway
|
|
d.SetId(*vpnGateway.VpnGatewayId)
|
|
log.Printf("[INFO] VPN Gateway ID: %s", *vpnGateway.VpnGatewayId)
|
|
|
|
// Attach the VPN gateway to the correct VPC
|
|
return resourceAwsVpnGatewayUpdate(d, meta)
|
|
}
|
|
|
|
func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{
|
|
VpnGatewayIds: []*string{aws.String(d.Id())},
|
|
})
|
|
if err != nil {
|
|
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnGatewayID.NotFound" {
|
|
d.SetId("")
|
|
return nil
|
|
} else {
|
|
log.Printf("[ERROR] Error finding VpnGateway: %s", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
vpnGateway := resp.VpnGateways[0]
|
|
if vpnGateway == nil || *vpnGateway.State == "deleted" {
|
|
// Seems we have lost our VPN gateway
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
|
|
vpnAttachment := vpnGatewayGetAttachment(vpnGateway)
|
|
if len(vpnGateway.VpcAttachments) == 0 || *vpnAttachment.State == "detached" {
|
|
// Gateway exists but not attached to the VPC
|
|
d.Set("vpc_id", "")
|
|
} else {
|
|
d.Set("vpc_id", *vpnAttachment.VpcId)
|
|
}
|
|
|
|
if vpnGateway.AvailabilityZone != nil && *vpnGateway.AvailabilityZone != "" {
|
|
d.Set("availability_zone", vpnGateway.AvailabilityZone)
|
|
}
|
|
d.Set("tags", tagsToMap(vpnGateway.Tags))
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
if d.HasChange("vpc_id") {
|
|
// If we're already attached, detach it first
|
|
if err := resourceAwsVpnGatewayDetach(d, meta); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Attach the VPN gateway to the new vpc
|
|
if err := resourceAwsVpnGatewayAttach(d, meta); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
if err := setTags(conn, d); err != nil {
|
|
return err
|
|
}
|
|
|
|
d.SetPartial("tags")
|
|
|
|
return resourceAwsVpnGatewayRead(d, meta)
|
|
}
|
|
|
|
func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
// Detach if it is attached
|
|
if err := resourceAwsVpnGatewayDetach(d, meta); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("[INFO] Deleting VPN gateway: %s", d.Id())
|
|
|
|
return resource.Retry(5*time.Minute, func() *resource.RetryError {
|
|
_, err := conn.DeleteVpnGateway(&ec2.DeleteVpnGatewayInput{
|
|
VpnGatewayId: aws.String(d.Id()),
|
|
})
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
ec2err, ok := err.(awserr.Error)
|
|
if !ok {
|
|
return resource.RetryableError(err)
|
|
}
|
|
|
|
switch ec2err.Code() {
|
|
case "InvalidVpnGatewayID.NotFound":
|
|
return nil
|
|
case "IncorrectState":
|
|
return resource.RetryableError(err)
|
|
}
|
|
|
|
return resource.NonRetryableError(err)
|
|
})
|
|
}
|
|
|
|
func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
if d.Get("vpc_id").(string) == "" {
|
|
log.Printf(
|
|
"[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set",
|
|
d.Id())
|
|
return nil
|
|
}
|
|
|
|
log.Printf(
|
|
"[INFO] Attaching VPN Gateway '%s' to VPC '%s'",
|
|
d.Id(),
|
|
d.Get("vpc_id").(string))
|
|
|
|
req := &ec2.AttachVpnGatewayInput{
|
|
VpnGatewayId: aws.String(d.Id()),
|
|
VpcId: aws.String(d.Get("vpc_id").(string)),
|
|
}
|
|
|
|
err := resource.Retry(30*time.Second, func() *resource.RetryError {
|
|
_, err := conn.AttachVpnGateway(req)
|
|
if err != nil {
|
|
if ec2err, ok := err.(awserr.Error); ok {
|
|
if "InvalidVpnGatewayID.NotFound" == ec2err.Code() {
|
|
return resource.RetryableError(
|
|
fmt.Errorf("Gateway not found, retry for eventual consistancy"))
|
|
}
|
|
}
|
|
return resource.NonRetryableError(err)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Wait for it to be fully attached before continuing
|
|
log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id())
|
|
stateConf := &resource.StateChangeConf{
|
|
Pending: []string{"detached", "attaching"},
|
|
Target: []string{"attached"},
|
|
Refresh: vpnGatewayAttachStateRefreshFunc(conn, d.Id(), "available"),
|
|
Timeout: 1 * time.Minute,
|
|
}
|
|
if _, err := stateConf.WaitForState(); err != nil {
|
|
return fmt.Errorf(
|
|
"Error waiting for VPN gateway (%s) to attach: %s",
|
|
d.Id(), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).ec2conn
|
|
|
|
// Get the old VPC ID to detach from
|
|
vpcID, _ := d.GetChange("vpc_id")
|
|
|
|
if vpcID.(string) == "" {
|
|
log.Printf(
|
|
"[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set",
|
|
d.Id())
|
|
return nil
|
|
}
|
|
|
|
log.Printf(
|
|
"[INFO] Detaching VPN Gateway '%s' from VPC '%s'",
|
|
d.Id(),
|
|
vpcID.(string))
|
|
|
|
wait := true
|
|
_, err := conn.DetachVpnGateway(&ec2.DetachVpnGatewayInput{
|
|
VpnGatewayId: aws.String(d.Id()),
|
|
VpcId: aws.String(vpcID.(string)),
|
|
})
|
|
if err != nil {
|
|
ec2err, ok := err.(awserr.Error)
|
|
if ok {
|
|
if ec2err.Code() == "InvalidVpnGatewayID.NotFound" {
|
|
err = nil
|
|
wait = false
|
|
} else if ec2err.Code() == "InvalidVpnGatewayAttachment.NotFound" {
|
|
err = nil
|
|
wait = false
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !wait {
|
|
return nil
|
|
}
|
|
|
|
// Wait for it to be fully detached before continuing
|
|
log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id())
|
|
stateConf := &resource.StateChangeConf{
|
|
Pending: []string{"attached", "detaching", "available"},
|
|
Target: []string{"detached"},
|
|
Refresh: vpnGatewayAttachStateRefreshFunc(conn, d.Id(), "detached"),
|
|
Timeout: 1 * time.Minute,
|
|
}
|
|
if _, err := stateConf.WaitForState(); err != nil {
|
|
return fmt.Errorf(
|
|
"Error waiting for vpn gateway (%s) to detach: %s",
|
|
d.Id(), err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// vpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
|
// the state of a VPN gateway's attachment
|
|
func vpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc {
|
|
var start time.Time
|
|
return func() (interface{}, string, error) {
|
|
if start.IsZero() {
|
|
start = time.Now()
|
|
}
|
|
|
|
resp, err := conn.DescribeVpnGateways(&ec2.DescribeVpnGatewaysInput{
|
|
VpnGatewayIds: []*string{aws.String(id)},
|
|
})
|
|
|
|
if err != nil {
|
|
if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnGatewayID.NotFound" {
|
|
resp = nil
|
|
} else {
|
|
log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err)
|
|
return nil, "", err
|
|
}
|
|
}
|
|
|
|
if resp == nil {
|
|
// Sometimes AWS just has consistency issues and doesn't see
|
|
// our instance yet. Return an empty state.
|
|
return nil, "", nil
|
|
}
|
|
|
|
vpnGateway := resp.VpnGateways[0]
|
|
if len(vpnGateway.VpcAttachments) == 0 {
|
|
// No attachments, we're detached
|
|
return vpnGateway, "detached", nil
|
|
}
|
|
|
|
vpnAttachment := vpnGatewayGetAttachment(vpnGateway)
|
|
return vpnGateway, *vpnAttachment.State, nil
|
|
}
|
|
}
|
|
|
|
func vpnGatewayGetAttachment(vgw *ec2.VpnGateway) *ec2.VpcAttachment {
|
|
for _, v := range vgw.VpcAttachments {
|
|
if *v.State == "attached" {
|
|
return v
|
|
}
|
|
}
|
|
return &ec2.VpcAttachment{State: aws.String("detached")}
|
|
}
|