From 12d480ee5daeffaa75fb256d246a8d30463bcf30 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 10 Oct 2017 11:38:43 +0100 Subject: [PATCH] r/vpn_connection_route: Wait until route is available/deleted --- aws/resource_aws_vpn_connection_route.go | 134 ++++++++++++++--------- 1 file changed, 84 insertions(+), 50 deletions(-) diff --git a/aws/resource_aws_vpn_connection_route.go b/aws/resource_aws_vpn_connection_route.go index 67b3cf1c2bca..0f1991fe658d 100644 --- a/aws/resource_aws_vpn_connection_route.go +++ b/aws/resource_aws_vpn_connection_route.go @@ -4,11 +4,13 @@ import ( "fmt" "log" "strings" + "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" ) @@ -39,9 +41,11 @@ func resourceAwsVpnConnectionRoute() *schema.Resource { func resourceAwsVpnConnectionRouteCreate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn + cidrBlock := d.Get("destination_cidr_block").(string) + vpnConnectionId := d.Get("vpn_connection_id").(string) createOpts := &ec2.CreateVpnConnectionRouteInput{ - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - VpnConnectionId: aws.String(d.Get("vpn_connection_id").(string)), + DestinationCidrBlock: aws.String(cidrBlock), + VpnConnectionId: aws.String(vpnConnectionId), } // Create the route. @@ -54,6 +58,23 @@ func resourceAwsVpnConnectionRouteCreate(d *schema.ResourceData, meta interface{ // Store the ID by the only two data we have available to us. d.SetId(fmt.Sprintf("%s:%s", *createOpts.DestinationCidrBlock, *createOpts.VpnConnectionId)) + stateConf := resource.StateChangeConf{ + Pending: []string{"pending"}, + Target: []string{"available"}, + Timeout: 15 * time.Second, + Refresh: func() (interface{}, string, error) { + route, err := findConnectionRoute(conn, cidrBlock, vpnConnectionId) + if err != nil { + return 42, "", err + } + return route, *route.State, nil + }, + } + _, err = stateConf.WaitForState() + if err != nil { + return err + } + return resourceAwsVpnConnectionRouteRead(d, meta) } @@ -62,51 +83,11 @@ func resourceAwsVpnConnectionRouteRead(d *schema.ResourceData, meta interface{}) cidrBlock, vpnConnectionId := resourceAwsVpnConnectionRouteParseId(d.Id()) - routeFilters := []*ec2.Filter{ - &ec2.Filter{ - Name: aws.String("route.destination-cidr-block"), - Values: []*string{aws.String(cidrBlock)}, - }, - &ec2.Filter{ - Name: aws.String("vpn-connection-id"), - Values: []*string{aws.String(vpnConnectionId)}, - }, - } - - // Technically, we know everything there is to know about the route - // from its ID, but we still want to catch cases where it changes - // outside of terraform and results in a stale state file. Hence, - // conduct a read. - resp, err := conn.DescribeVpnConnections(&ec2.DescribeVpnConnectionsInput{ - Filters: routeFilters, - }) + route, err := findConnectionRoute(conn, cidrBlock, vpnConnectionId) if err != nil { - if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" { - d.SetId("") - return nil - } else { - log.Printf("[ERROR] Error finding VPN connection route: %s", err) - return err - } - } - if resp == nil || len(resp.VpnConnections) == 0 { - // This is kind of a weird edge case. I'd rather return an error - // instead of just blindly setting the ID to ""... since I don't know - // what might cause this. - return fmt.Errorf("No VPN connections returned") - } - - vpnConnection := resp.VpnConnections[0] - - var found bool - for _, r := range vpnConnection.Routes { - if *r.DestinationCidrBlock == cidrBlock && *r.State != "deleted" { - d.Set("destination_cidr_block", *r.DestinationCidrBlock) - d.Set("vpn_connection_id", *vpnConnection.VpnConnectionId) - found = true - } + return err } - if !found { + if route == nil { // Something other than terraform eliminated the route. d.SetId("") } @@ -117,23 +98,76 @@ func resourceAwsVpnConnectionRouteRead(d *schema.ResourceData, meta interface{}) func resourceAwsVpnConnectionRouteDelete(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn + cidrBlock := d.Get("destination_cidr_block").(string) + vpnConnectionId := d.Get("vpn_connection_id").(string) _, err := conn.DeleteVpnConnectionRoute(&ec2.DeleteVpnConnectionRouteInput{ - DestinationCidrBlock: aws.String(d.Get("destination_cidr_block").(string)), - VpnConnectionId: aws.String(d.Get("vpn_connection_id").(string)), + DestinationCidrBlock: aws.String(cidrBlock), + VpnConnectionId: aws.String(vpnConnectionId), }) if err != nil { if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" { d.SetId("") return nil - } else { - log.Printf("[ERROR] Error deleting VPN connection route: %s", err) - return err } + log.Printf("[ERROR] Error deleting VPN connection route: %s", err) + return err + } + + stateConf := resource.StateChangeConf{ + Pending: []string{"pending", "available", "deleting"}, + Target: []string{"deleted"}, + Timeout: 15 * time.Second, + Refresh: func() (interface{}, string, error) { + route, err := findConnectionRoute(conn, cidrBlock, vpnConnectionId) + if err != nil { + return 42, "", err + } + if route == nil { + return 42, "deleted", nil + } + return route, *route.State, nil + }, + } + _, err = stateConf.WaitForState() + if err != nil { + return err } return nil } +func findConnectionRoute(conn *ec2.EC2, cidrBlock, vpnConnectionId string) (*ec2.VpnStaticRoute, error) { + resp, err := conn.DescribeVpnConnections(&ec2.DescribeVpnConnectionsInput{ + Filters: []*ec2.Filter{ + &ec2.Filter{ + Name: aws.String("route.destination-cidr-block"), + Values: []*string{aws.String(cidrBlock)}, + }, + &ec2.Filter{ + Name: aws.String("vpn-connection-id"), + Values: []*string{aws.String(vpnConnectionId)}, + }, + }, + }) + if err != nil { + if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" { + return nil, nil + } + return nil, err + } + if resp == nil || len(resp.VpnConnections) == 0 { + return nil, nil + } + vpnConnection := resp.VpnConnections[0] + + for _, r := range vpnConnection.Routes { + if *r.DestinationCidrBlock == cidrBlock && *r.State != "deleted" { + return r, nil + } + } + return nil, nil +} + func resourceAwsVpnConnectionRouteParseId(id string) (string, string) { parts := strings.SplitN(id, ":", 2) return parts[0], parts[1]