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