github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  
   298  	// Set attributes under the user's control.
   299  	d.Set("vpn_gateway_id", vpnConnection.VpnGatewayId)
   300  	d.Set("customer_gateway_id", vpnConnection.CustomerGatewayId)
   301  	d.Set("type", vpnConnection.Type)
   302  	d.Set("tags", tagsToMap(vpnConnection.Tags))
   303  
   304  	if vpnConnection.Options != nil {
   305  		if err := d.Set("static_routes_only", vpnConnection.Options.StaticRoutesOnly); err != nil {
   306  			return err
   307  		}
   308  	} else {
   309  		//If there no Options on the connection then we do not support *static_routes*
   310  		d.Set("static_routes_only", false)
   311  	}
   312  
   313  	// Set read only attributes.
   314  	d.Set("customer_gateway_configuration", vpnConnection.CustomerGatewayConfiguration)
   315  
   316  	if vpnConnection.CustomerGatewayConfiguration != nil {
   317  		if tunnelInfo, err := xmlConfigToTunnelInfo(*vpnConnection.CustomerGatewayConfiguration); err != nil {
   318  			log.Printf("[ERR] Error unmarshaling XML configuration for (%s): %s", d.Id(), err)
   319  		} else {
   320  			d.Set("tunnel1_address", tunnelInfo.Tunnel1Address)
   321  			d.Set("tunnel1_preshared_key", tunnelInfo.Tunnel1PreSharedKey)
   322  			d.Set("tunnel2_address", tunnelInfo.Tunnel2Address)
   323  			d.Set("tunnel2_preshared_key", tunnelInfo.Tunnel2PreSharedKey)
   324  		}
   325  	}
   326  
   327  	if err := d.Set("vgw_telemetry", telemetryToMapList(vpnConnection.VgwTelemetry)); err != nil {
   328  		return err
   329  	}
   330  	if err := d.Set("routes", routesToMapList(vpnConnection.Routes)); err != nil {
   331  		return err
   332  	}
   333  
   334  	return nil
   335  }
   336  
   337  func resourceAwsVpnConnectionUpdate(d *schema.ResourceData, meta interface{}) error {
   338  	conn := meta.(*AWSClient).ec2conn
   339  
   340  	// Update tags if required.
   341  	if err := setTags(conn, d); err != nil {
   342  		return err
   343  	}
   344  
   345  	d.SetPartial("tags")
   346  
   347  	return resourceAwsVpnConnectionRead(d, meta)
   348  }
   349  
   350  func resourceAwsVpnConnectionDelete(d *schema.ResourceData, meta interface{}) error {
   351  	conn := meta.(*AWSClient).ec2conn
   352  
   353  	_, err := conn.DeleteVpnConnection(&ec2.DeleteVpnConnectionInput{
   354  		VpnConnectionId: aws.String(d.Id()),
   355  	})
   356  	if err != nil {
   357  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpnConnectionID.NotFound" {
   358  			d.SetId("")
   359  			return nil
   360  		} else {
   361  			log.Printf("[ERROR] Error deleting VPN connection: %s", err)
   362  			return err
   363  		}
   364  	}
   365  
   366  	// These things can take quite a while to tear themselves down and any
   367  	// attempt to modify resources they reference (e.g. CustomerGateways or
   368  	// VPN Gateways) before deletion will result in an error. Furthermore,
   369  	// they don't just disappear. The go into "deleted" state. We need to
   370  	// wait to ensure any other modifications the user might make to their
   371  	// VPC stack can safely run.
   372  	stateConf := &resource.StateChangeConf{
   373  		Pending:    []string{"deleting"},
   374  		Target:     []string{"deleted"},
   375  		Refresh:    vpnConnectionRefreshFunc(conn, d.Id()),
   376  		Timeout:    30 * time.Minute,
   377  		Delay:      10 * time.Second,
   378  		MinTimeout: 10 * time.Second,
   379  	}
   380  
   381  	_, stateErr := stateConf.WaitForState()
   382  	if stateErr != nil {
   383  		return fmt.Errorf(
   384  			"Error waiting for VPN connection (%s) to delete: %s", d.Id(), err)
   385  	}
   386  
   387  	return nil
   388  }
   389  
   390  // routesToMapList turns the list of routes into a list of maps.
   391  func routesToMapList(routes []*ec2.VpnStaticRoute) []map[string]interface{} {
   392  	result := make([]map[string]interface{}, 0, len(routes))
   393  	for _, r := range routes {
   394  		staticRoute := make(map[string]interface{})
   395  		staticRoute["destination_cidr_block"] = *r.DestinationCidrBlock
   396  		staticRoute["state"] = *r.State
   397  
   398  		if r.Source != nil {
   399  			staticRoute["source"] = *r.Source
   400  		}
   401  
   402  		result = append(result, staticRoute)
   403  	}
   404  
   405  	return result
   406  }
   407  
   408  // telemetryToMapList turns the VGW telemetry into a list of maps.
   409  func telemetryToMapList(telemetry []*ec2.VgwTelemetry) []map[string]interface{} {
   410  	result := make([]map[string]interface{}, 0, len(telemetry))
   411  	for _, t := range telemetry {
   412  		vgw := make(map[string]interface{})
   413  		vgw["accepted_route_count"] = *t.AcceptedRouteCount
   414  		vgw["outside_ip_address"] = *t.OutsideIpAddress
   415  		vgw["status"] = *t.Status
   416  		vgw["status_message"] = *t.StatusMessage
   417  
   418  		// LastStatusChange is a time.Time(). Convert it into a string
   419  		// so it can be handled by schema's type system.
   420  		vgw["last_status_change"] = t.LastStatusChange.String()
   421  		result = append(result, vgw)
   422  	}
   423  
   424  	return result
   425  }
   426  
   427  func xmlConfigToTunnelInfo(xmlConfig string) (*TunnelInfo, error) {
   428  	var vpnConfig XmlVpnConnectionConfig
   429  	if err := xml.Unmarshal([]byte(xmlConfig), &vpnConfig); err != nil {
   430  		return nil, errwrap.Wrapf("Error Unmarshalling XML: {{err}}", err)
   431  	}
   432  
   433  	// don't expect consistent ordering from the XML
   434  	sort.Sort(vpnConfig)
   435  
   436  	tunnelInfo := TunnelInfo{
   437  		Tunnel1Address:      vpnConfig.Tunnels[0].OutsideAddress,
   438  		Tunnel1PreSharedKey: vpnConfig.Tunnels[0].PreSharedKey,
   439  
   440  		Tunnel2Address:      vpnConfig.Tunnels[1].OutsideAddress,
   441  		Tunnel2PreSharedKey: vpnConfig.Tunnels[1].PreSharedKey,
   442  	}
   443  
   444  	return &tunnelInfo, nil
   445  }