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