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