github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_vpn_connection.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/xml"
     6  	"fmt"
     7  	"log"
     8  	"sort"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    14  
    15  	"github.com/hashicorp/errwrap"
    16  	"github.com/hashicorp/terraform/helper/hashcode"
    17  	"github.com/hashicorp/terraform/helper/resource"
    18  	"github.com/hashicorp/terraform/helper/schema"
    19  )
    20  
    21  type XmlVpnConnectionConfig struct {
    22  	Tunnels []XmlIpsecTunnel `xml:"ipsec_tunnel"`
    23  }
    24  
    25  type XmlIpsecTunnel struct {
    26  	OutsideAddress string `xml:"vpn_gateway>tunnel_outside_address>ip_address"`
    27  	PreSharedKey   string `xml:"ike>pre_shared_key"`
    28  }
    29  
    30  type TunnelInfo struct {
    31  	Tunnel1Address      string
    32  	Tunnel1PreSharedKey string
    33  	Tunnel2Address      string
    34  	Tunnel2PreSharedKey string
    35  }
    36  
    37  func (slice XmlVpnConnectionConfig) Len() int {
    38  	return len(slice.Tunnels)
    39  }
    40  
    41  func (slice XmlVpnConnectionConfig) Less(i, j int) bool {
    42  	return slice.Tunnels[i].OutsideAddress < slice.Tunnels[j].OutsideAddress
    43  }
    44  
    45  func (slice XmlVpnConnectionConfig) Swap(i, j int) {
    46  	slice.Tunnels[i], slice.Tunnels[j] = slice.Tunnels[j], slice.Tunnels[i]
    47  }
    48  
    49  func resourceAwsVpnConnection() *schema.Resource {
    50  	return &schema.Resource{
    51  		Create: resourceAwsVpnConnectionCreate,
    52  		Read:   resourceAwsVpnConnectionRead,
    53  		Update: resourceAwsVpnConnectionUpdate,
    54  		Delete: resourceAwsVpnConnectionDelete,
    55  		Importer: &schema.ResourceImporter{
    56  			State: schema.ImportStatePassthrough,
    57  		},
    58  
    59  		Schema: map[string]*schema.Schema{
    60  			"vpn_gateway_id": {
    61  				Type:     schema.TypeString,
    62  				Required: true,
    63  				ForceNew: true,
    64  			},
    65  
    66  			"customer_gateway_id": {
    67  				Type:     schema.TypeString,
    68  				Required: true,
    69  				ForceNew: true,
    70  			},
    71  
    72  			"type": {
    73  				Type:     schema.TypeString,
    74  				Required: true,
    75  				ForceNew: true,
    76  			},
    77  
    78  			"static_routes_only": {
    79  				Type:     schema.TypeBool,
    80  				Optional: true,
    81  				Computed: true,
    82  				ForceNew: true,
    83  			},
    84  
    85  			"tags": tagsSchema(),
    86  
    87  			// Begin read only attributes
    88  			"customer_gateway_configuration": {
    89  				Type:     schema.TypeString,
    90  				Computed: true,
    91  				Optional: true,
    92  			},
    93  
    94  			"tunnel1_address": {
    95  				Type:     schema.TypeString,
    96  				Computed: true,
    97  			},
    98  
    99  			"tunnel1_preshared_key": {
   100  				Type:     schema.TypeString,
   101  				Computed: true,
   102  			},
   103  
   104  			"tunnel2_address": {
   105  				Type:     schema.TypeString,
   106  				Computed: true,
   107  			},
   108  
   109  			"tunnel2_preshared_key": {
   110  				Type:     schema.TypeString,
   111  				Computed: true,
   112  			},
   113  
   114  			"routes": {
   115  				Type:     schema.TypeSet,
   116  				Computed: true,
   117  				Optional: true,
   118  				Elem: &schema.Resource{
   119  					Schema: map[string]*schema.Schema{
   120  						"destination_cidr_block": {
   121  							Type:     schema.TypeString,
   122  							Computed: true,
   123  							Optional: true,
   124  						},
   125  
   126  						"source": {
   127  							Type:     schema.TypeString,
   128  							Computed: true,
   129  							Optional: true,
   130  						},
   131  
   132  						"state": {
   133  							Type:     schema.TypeString,
   134  							Computed: true,
   135  							Optional: true,
   136  						},
   137  					},
   138  				},
   139  				Set: func(v interface{}) int {
   140  					var buf bytes.Buffer
   141  					m := v.(map[string]interface{})
   142  					buf.WriteString(fmt.Sprintf("%s-", m["destination_cidr_block"].(string)))
   143  					buf.WriteString(fmt.Sprintf("%s-", m["source"].(string)))
   144  					buf.WriteString(fmt.Sprintf("%s-", m["state"].(string)))
   145  					return hashcode.String(buf.String())
   146  				},
   147  			},
   148  
   149  			"vgw_telemetry": {
   150  				Type:     schema.TypeSet,
   151  				Computed: true,
   152  				Optional: true,
   153  				Elem: &schema.Resource{
   154  					Schema: map[string]*schema.Schema{
   155  						"accepted_route_count": {
   156  							Type:     schema.TypeInt,
   157  							Computed: true,
   158  							Optional: true,
   159  						},
   160  
   161  						"last_status_change": {
   162  							Type:     schema.TypeString,
   163  							Computed: true,
   164  							Optional: true,
   165  						},
   166  
   167  						"outside_ip_address": {
   168  							Type:     schema.TypeString,
   169  							Computed: true,
   170  							Optional: true,
   171  						},
   172  
   173  						"status": {
   174  							Type:     schema.TypeString,
   175  							Computed: true,
   176  							Optional: true,
   177  						},
   178  
   179  						"status_message": {
   180  							Type:     schema.TypeString,
   181  							Computed: true,
   182  							Optional: true,
   183  						},
   184  					},
   185  				},
   186  				Set: func(v interface{}) int {
   187  					var buf bytes.Buffer
   188  					m := v.(map[string]interface{})
   189  					buf.WriteString(fmt.Sprintf("%s-", m["outside_ip_address"].(string)))
   190  					return hashcode.String(buf.String())
   191  				},
   192  			},
   193  		},
   194  	}
   195  }
   196  
   197  func resourceAwsVpnConnectionCreate(d *schema.ResourceData, meta interface{}) error {
   198  	conn := meta.(*AWSClient).ec2conn
   199  
   200  	connectOpts := &ec2.VpnConnectionOptionsSpecification{
   201  		StaticRoutesOnly: aws.Bool(d.Get("static_routes_only").(bool)),
   202  	}
   203  
   204  	createOpts := &ec2.CreateVpnConnectionInput{
   205  		CustomerGatewayId: aws.String(d.Get("customer_gateway_id").(string)),
   206  		Options:           connectOpts,
   207  		Type:              aws.String(d.Get("type").(string)),
   208  		VpnGatewayId:      aws.String(d.Get("vpn_gateway_id").(string)),
   209  	}
   210  
   211  	// Create the VPN Connection
   212  	log.Printf("[DEBUG] Creating vpn connection")
   213  	resp, err := conn.CreateVpnConnection(createOpts)
   214  	if err != nil {
   215  		return fmt.Errorf("Error creating vpn connection: %s", err)
   216  	}
   217  
   218  	// Store the ID
   219  	vpnConnection := resp.VpnConnection
   220  	d.SetId(*vpnConnection.VpnConnectionId)
   221  	log.Printf("[INFO] VPN connection ID: %s", *vpnConnection.VpnConnectionId)
   222  
   223  	// Wait for the connection to become available. This has an obscenely
   224  	// high default timeout because AWS VPN connections are notoriously
   225  	// slow at coming up or going down. There's also no point in checking
   226  	// more frequently than every ten seconds.
   227  	stateConf := &resource.StateChangeConf{
   228  		Pending:    []string{"pending"},
   229  		Target:     []string{"available"},
   230  		Refresh:    vpnConnectionRefreshFunc(conn, *vpnConnection.VpnConnectionId),
   231  		Timeout:    30 * time.Minute,
   232  		Delay:      10 * time.Second,
   233  		MinTimeout: 10 * time.Second,
   234  	}
   235  
   236  	_, stateErr := stateConf.WaitForState()
   237  	if stateErr != nil {
   238  		return fmt.Errorf(
   239  			"Error waiting for VPN connection (%s) to become ready: %s",
   240  			*vpnConnection.VpnConnectionId, err)
   241  	}
   242  
   243  	// Create tags.
   244  	if err := setTags(conn, d); err != nil {
   245  		return err
   246  	}
   247  
   248  	// Read off the API to populate our RO fields.
   249  	return resourceAwsVpnConnectionRead(d, meta)
   250  }
   251  
   252  func vpnConnectionRefreshFunc(conn *ec2.EC2, connectionId string) resource.StateRefreshFunc {
   253  	return func() (interface{}, string, error) {
   254  		resp, err := conn.DescribeVpnConnections(&ec2.DescribeVpnConnectionsInput{
   255  			VpnConnectionIds: []*string{aws.String(connectionId)},
   256  		})
   257  
   258  		if err != nil {
   259  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" {
   260  				resp = nil
   261  			} else {
   262  				log.Printf("Error on VPNConnectionRefresh: %s", err)
   263  				return nil, "", err
   264  			}
   265  		}
   266  
   267  		if resp == nil || len(resp.VpnConnections) == 0 {
   268  			return nil, "", nil
   269  		}
   270  
   271  		connection := resp.VpnConnections[0]
   272  		return connection, *connection.State, nil
   273  	}
   274  }
   275  
   276  func resourceAwsVpnConnectionRead(d *schema.ResourceData, meta interface{}) error {
   277  	conn := meta.(*AWSClient).ec2conn
   278  
   279  	resp, err := conn.DescribeVpnConnections(&ec2.DescribeVpnConnectionsInput{
   280  		VpnConnectionIds: []*string{aws.String(d.Id())},
   281  	})
   282  	if err != nil {
   283  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" {
   284  			d.SetId("")
   285  			return nil
   286  		} else {
   287  			log.Printf("[ERROR] Error finding VPN connection: %s", err)
   288  			return err
   289  		}
   290  	}
   291  
   292  	if len(resp.VpnConnections) != 1 {
   293  		return fmt.Errorf("[ERROR] Error finding VPN connection: %s", d.Id())
   294  	}
   295  
   296  	vpnConnection := resp.VpnConnections[0]
   297  	if vpnConnection == nil || *vpnConnection.State == "deleted" {
   298  		// Seems we have lost our VPN Connection
   299  		d.SetId("")
   300  		return nil
   301  	}
   302  
   303  	// Set attributes under the user's control.
   304  	d.Set("vpn_gateway_id", vpnConnection.VpnGatewayId)
   305  	d.Set("customer_gateway_id", vpnConnection.CustomerGatewayId)
   306  	d.Set("type", vpnConnection.Type)
   307  	d.Set("tags", tagsToMap(vpnConnection.Tags))
   308  
   309  	if vpnConnection.Options != nil {
   310  		if err := d.Set("static_routes_only", vpnConnection.Options.StaticRoutesOnly); err != nil {
   311  			return err
   312  		}
   313  	} else {
   314  		//If there no Options on the connection then we do not support *static_routes*
   315  		d.Set("static_routes_only", false)
   316  	}
   317  
   318  	// Set read only attributes.
   319  	d.Set("customer_gateway_configuration", vpnConnection.CustomerGatewayConfiguration)
   320  
   321  	if vpnConnection.CustomerGatewayConfiguration != nil {
   322  		if tunnelInfo, err := xmlConfigToTunnelInfo(*vpnConnection.CustomerGatewayConfiguration); err != nil {
   323  			log.Printf("[ERR] Error unmarshaling XML configuration for (%s): %s", d.Id(), err)
   324  		} else {
   325  			d.Set("tunnel1_address", tunnelInfo.Tunnel1Address)
   326  			d.Set("tunnel1_preshared_key", tunnelInfo.Tunnel1PreSharedKey)
   327  			d.Set("tunnel2_address", tunnelInfo.Tunnel2Address)
   328  			d.Set("tunnel2_preshared_key", tunnelInfo.Tunnel2PreSharedKey)
   329  		}
   330  	}
   331  
   332  	if err := d.Set("vgw_telemetry", telemetryToMapList(vpnConnection.VgwTelemetry)); err != nil {
   333  		return err
   334  	}
   335  	if err := d.Set("routes", routesToMapList(vpnConnection.Routes)); err != nil {
   336  		return err
   337  	}
   338  
   339  	return nil
   340  }
   341  
   342  func resourceAwsVpnConnectionUpdate(d *schema.ResourceData, meta interface{}) error {
   343  	conn := meta.(*AWSClient).ec2conn
   344  
   345  	// Update tags if required.
   346  	if err := setTags(conn, d); err != nil {
   347  		return err
   348  	}
   349  
   350  	d.SetPartial("tags")
   351  
   352  	return resourceAwsVpnConnectionRead(d, meta)
   353  }
   354  
   355  func resourceAwsVpnConnectionDelete(d *schema.ResourceData, meta interface{}) error {
   356  	conn := meta.(*AWSClient).ec2conn
   357  
   358  	_, err := conn.DeleteVpnConnection(&ec2.DeleteVpnConnectionInput{
   359  		VpnConnectionId: aws.String(d.Id()),
   360  	})
   361  	if err != nil {
   362  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" {
   363  			d.SetId("")
   364  			return nil
   365  		} else {
   366  			log.Printf("[ERROR] Error deleting VPN connection: %s", err)
   367  			return err
   368  		}
   369  	}
   370  
   371  	// These things can take quite a while to tear themselves down and any
   372  	// attempt to modify resources they reference (e.g. CustomerGateways or
   373  	// VPN Gateways) before deletion will result in an error. Furthermore,
   374  	// they don't just disappear. The go into "deleted" state. We need to
   375  	// wait to ensure any other modifications the user might make to their
   376  	// VPC stack can safely run.
   377  	stateConf := &resource.StateChangeConf{
   378  		Pending:    []string{"deleting"},
   379  		Target:     []string{"deleted"},
   380  		Refresh:    vpnConnectionRefreshFunc(conn, d.Id()),
   381  		Timeout:    30 * time.Minute,
   382  		Delay:      10 * time.Second,
   383  		MinTimeout: 10 * time.Second,
   384  	}
   385  
   386  	_, stateErr := stateConf.WaitForState()
   387  	if stateErr != nil {
   388  		return fmt.Errorf(
   389  			"Error waiting for VPN connection (%s) to delete: %s", d.Id(), err)
   390  	}
   391  
   392  	return nil
   393  }
   394  
   395  // routesToMapList turns the list of routes into a list of maps.
   396  func routesToMapList(routes []*ec2.VpnStaticRoute) []map[string]interface{} {
   397  	result := make([]map[string]interface{}, 0, len(routes))
   398  	for _, r := range routes {
   399  		staticRoute := make(map[string]interface{})
   400  		staticRoute["destination_cidr_block"] = *r.DestinationCidrBlock
   401  		staticRoute["state"] = *r.State
   402  
   403  		if r.Source != nil {
   404  			staticRoute["source"] = *r.Source
   405  		}
   406  
   407  		result = append(result, staticRoute)
   408  	}
   409  
   410  	return result
   411  }
   412  
   413  // telemetryToMapList turns the VGW telemetry into a list of maps.
   414  func telemetryToMapList(telemetry []*ec2.VgwTelemetry) []map[string]interface{} {
   415  	result := make([]map[string]interface{}, 0, len(telemetry))
   416  	for _, t := range telemetry {
   417  		vgw := make(map[string]interface{})
   418  		vgw["accepted_route_count"] = *t.AcceptedRouteCount
   419  		vgw["outside_ip_address"] = *t.OutsideIpAddress
   420  		vgw["status"] = *t.Status
   421  		vgw["status_message"] = *t.StatusMessage
   422  
   423  		// LastStatusChange is a time.Time(). Convert it into a string
   424  		// so it can be handled by schema's type system.
   425  		vgw["last_status_change"] = t.LastStatusChange.String()
   426  		result = append(result, vgw)
   427  	}
   428  
   429  	return result
   430  }
   431  
   432  func xmlConfigToTunnelInfo(xmlConfig string) (*TunnelInfo, error) {
   433  	var vpnConfig XmlVpnConnectionConfig
   434  	if err := xml.Unmarshal([]byte(xmlConfig), &vpnConfig); err != nil {
   435  		return nil, errwrap.Wrapf("Error Unmarshalling XML: {{err}}", err)
   436  	}
   437  
   438  	// don't expect consistent ordering from the XML
   439  	sort.Sort(vpnConfig)
   440  
   441  	tunnelInfo := TunnelInfo{
   442  		Tunnel1Address:      vpnConfig.Tunnels[0].OutsideAddress,
   443  		Tunnel1PreSharedKey: vpnConfig.Tunnels[0].PreSharedKey,
   444  
   445  		Tunnel2Address:      vpnConfig.Tunnels[1].OutsideAddress,
   446  		Tunnel2PreSharedKey: vpnConfig.Tunnels[1].PreSharedKey,
   447  	}
   448  
   449  	return &tunnelInfo, nil
   450  }