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