github.com/i0n/terraform@v0.4.3-0.20150506151324-010a39a58ec1/builtin/providers/aws/resource_aws_vpn_connection.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"time"
     8  
     9  	"github.com/awslabs/aws-sdk-go/aws"
    10  	"github.com/awslabs/aws-sdk-go/service/ec2"
    11  
    12  	"github.com/hashicorp/terraform/helper/hashcode"
    13  	"github.com/hashicorp/terraform/helper/resource"
    14  	"github.com/hashicorp/terraform/helper/schema"
    15  )
    16  
    17  func resourceAwsVpnConnection() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsVpnConnectionCreate,
    20  		Read:   resourceAwsVpnConnectionRead,
    21  		Update: resourceAwsVpnConnectionUpdate,
    22  		Delete: resourceAwsVpnConnectionDelete,
    23  
    24  		Schema: map[string]*schema.Schema{
    25  			"vpn_gateway_id": &schema.Schema{
    26  				Type:     schema.TypeString,
    27  				Required: true,
    28  				ForceNew: true,
    29  			},
    30  
    31  			"customer_gateway_id": &schema.Schema{
    32  				Type:     schema.TypeString,
    33  				Required: true,
    34  				ForceNew: true,
    35  			},
    36  
    37  			"type": &schema.Schema{
    38  				Type:     schema.TypeString,
    39  				Required: true,
    40  				ForceNew: true,
    41  			},
    42  
    43  			"static_routes_only": &schema.Schema{
    44  				Type:     schema.TypeBool,
    45  				Required: true,
    46  				ForceNew: true,
    47  			},
    48  
    49  			"tags": tagsSchema(),
    50  
    51  			// Begin read only attributes
    52  			"customer_gateway_configuration": &schema.Schema{
    53  				Type:     schema.TypeString,
    54  				Computed: true,
    55  				Optional: true,
    56  			},
    57  
    58  			"routes": &schema.Schema{
    59  				Type:     schema.TypeSet,
    60  				Computed: true,
    61  				Optional: true,
    62  				Elem: &schema.Resource{
    63  					Schema: map[string]*schema.Schema{
    64  						"destination_cidr_block": &schema.Schema{
    65  							Type:     schema.TypeString,
    66  							Computed: true,
    67  							Optional: true,
    68  						},
    69  
    70  						"source": &schema.Schema{
    71  							Type:     schema.TypeString,
    72  							Computed: true,
    73  							Optional: true,
    74  						},
    75  
    76  						"state": &schema.Schema{
    77  							Type:     schema.TypeString,
    78  							Computed: true,
    79  							Optional: true,
    80  						},
    81  					},
    82  				},
    83  				Set: func(v interface{}) int {
    84  					var buf bytes.Buffer
    85  					m := v.(map[string]interface{})
    86  					buf.WriteString(fmt.Sprintf("%s-", m["destination_cidr_block"].(string)))
    87  					buf.WriteString(fmt.Sprintf("%s-", m["source"].(string)))
    88  					buf.WriteString(fmt.Sprintf("%s-", m["state"].(string)))
    89  					return hashcode.String(buf.String())
    90  				},
    91  			},
    92  
    93  			"vgw_telemetry": &schema.Schema{
    94  				Type:     schema.TypeSet,
    95  				Computed: true,
    96  				Optional: true,
    97  				Elem: &schema.Resource{
    98  					Schema: map[string]*schema.Schema{
    99  						"accepted_route_count": &schema.Schema{
   100  							Type:     schema.TypeInt,
   101  							Computed: true,
   102  							Optional: true,
   103  						},
   104  
   105  						"last_status_change": &schema.Schema{
   106  							Type:     schema.TypeString,
   107  							Computed: true,
   108  							Optional: true,
   109  						},
   110  
   111  						"outside_ip_address": &schema.Schema{
   112  							Type:     schema.TypeString,
   113  							Computed: true,
   114  							Optional: true,
   115  						},
   116  
   117  						"status": &schema.Schema{
   118  							Type:     schema.TypeString,
   119  							Computed: true,
   120  							Optional: true,
   121  						},
   122  
   123  						"status_message": &schema.Schema{
   124  							Type:     schema.TypeString,
   125  							Computed: true,
   126  							Optional: true,
   127  						},
   128  					},
   129  				},
   130  				Set: func(v interface{}) int {
   131  					var buf bytes.Buffer
   132  					m := v.(map[string]interface{})
   133  					buf.WriteString(fmt.Sprintf("%s-", m["outside_ip_address"].(string)))
   134  					return hashcode.String(buf.String())
   135  				},
   136  			},
   137  		},
   138  	}
   139  }
   140  
   141  func resourceAwsVpnConnectionCreate(d *schema.ResourceData, meta interface{}) error {
   142  	conn := meta.(*AWSClient).ec2conn
   143  
   144  	connectOpts := &ec2.VPNConnectionOptionsSpecification{
   145  		StaticRoutesOnly: aws.Boolean(d.Get("static_routes_only").(bool)),
   146  	}
   147  
   148  	createOpts := &ec2.CreateVPNConnectionInput{
   149  		CustomerGatewayID: aws.String(d.Get("customer_gateway_id").(string)),
   150  		Options:           connectOpts,
   151  		Type:              aws.String(d.Get("type").(string)),
   152  		VPNGatewayID:      aws.String(d.Get("vpn_gateway_id").(string)),
   153  	}
   154  
   155  	// Create the VPN Connection
   156  	log.Printf("[DEBUG] Creating vpn connection")
   157  	resp, err := conn.CreateVPNConnection(createOpts)
   158  	if err != nil {
   159  		return fmt.Errorf("Error creating vpn connection: %s", err)
   160  	}
   161  
   162  	// Store the ID
   163  	vpnConnection := resp.VPNConnection
   164  	d.SetId(*vpnConnection.VPNConnectionID)
   165  	log.Printf("[INFO] VPN connection ID: %s", *vpnConnection.VPNConnectionID)
   166  
   167  	// Wait for the connection to become available. This has an obscenely
   168  	// high default timeout because AWS VPN connections are notoriously
   169  	// slow at coming up or going down. There's also no point in checking
   170  	// more frequently than every ten seconds.
   171  	stateConf := &resource.StateChangeConf{
   172  		Pending:    []string{"pending"},
   173  		Target:     "available",
   174  		Refresh:    vpnConnectionRefreshFunc(conn, *vpnConnection.VPNConnectionID),
   175  		Timeout:    30 * time.Minute,
   176  		Delay:      10 * time.Second,
   177  		MinTimeout: 10 * time.Second,
   178  	}
   179  
   180  	_, stateErr := stateConf.WaitForState()
   181  	if stateErr != nil {
   182  		return fmt.Errorf(
   183  			"Error waiting for VPN connection (%s) to become ready: %s",
   184  			*vpnConnection.VPNConnectionID, err)
   185  	}
   186  
   187  	// Create tags.
   188  	if err := setTagsSDK(conn, d); err != nil {
   189  		return err
   190  	}
   191  
   192  	// Read off the API to populate our RO fields.
   193  	return resourceAwsVpnConnectionRead(d, meta)
   194  }
   195  
   196  func vpnConnectionRefreshFunc(conn *ec2.EC2, connectionId string) resource.StateRefreshFunc {
   197  	return func() (interface{}, string, error) {
   198  		resp, err := conn.DescribeVPNConnections(&ec2.DescribeVPNConnectionsInput{
   199  			VPNConnectionIDs: []*string{aws.String(connectionId)},
   200  		})
   201  
   202  		if err != nil {
   203  			if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" {
   204  				resp = nil
   205  			} else {
   206  				log.Printf("Error on VPNConnectionRefresh: %s", err)
   207  				return nil, "", err
   208  			}
   209  		}
   210  
   211  		if resp == nil || len(resp.VPNConnections) == 0 {
   212  			return nil, "", nil
   213  		}
   214  
   215  		connection := resp.VPNConnections[0]
   216  		return connection, *connection.State, nil
   217  	}
   218  }
   219  
   220  func resourceAwsVpnConnectionRead(d *schema.ResourceData, meta interface{}) error {
   221  	conn := meta.(*AWSClient).ec2conn
   222  
   223  	resp, err := conn.DescribeVPNConnections(&ec2.DescribeVPNConnectionsInput{
   224  		VPNConnectionIDs: []*string{aws.String(d.Id())},
   225  	})
   226  	if err != nil {
   227  		if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" {
   228  			d.SetId("")
   229  			return nil
   230  		} else {
   231  			log.Printf("[ERROR] Error finding VPN connection: %s", err)
   232  			return err
   233  		}
   234  	}
   235  
   236  	if len(resp.VPNConnections) != 1 {
   237  		return fmt.Errorf("[ERROR] Error finding VPN connection: %s", d.Id())
   238  	}
   239  
   240  	vpnConnection := resp.VPNConnections[0]
   241  
   242  	// Set attributes under the user's control.
   243  	d.Set("vpn_gateway_id", vpnConnection.VPNGatewayID)
   244  	d.Set("customer_gateway_id", vpnConnection.CustomerGatewayID)
   245  	d.Set("type", vpnConnection.Type)
   246  	d.Set("tags", tagsToMapSDK(vpnConnection.Tags))
   247  
   248  	if vpnConnection.Options != nil {
   249  		if err := d.Set("static_routes_only", vpnConnection.Options.StaticRoutesOnly); err != nil {
   250  			return err
   251  		}
   252  	}
   253  
   254  	// Set read only attributes.
   255  	d.Set("customer_gateway_configuration", vpnConnection.CustomerGatewayConfiguration)
   256  	if err := d.Set("vgw_telemetry", telemetryToMapList(vpnConnection.VGWTelemetry)); err != nil {
   257  		return err
   258  	}
   259  	if vpnConnection.Routes != nil {
   260  		if err := d.Set("routes", routesToMapList(vpnConnection.Routes)); err != nil {
   261  			return err
   262  		}
   263  	}
   264  
   265  	return nil
   266  }
   267  
   268  func resourceAwsVpnConnectionUpdate(d *schema.ResourceData, meta interface{}) error {
   269  	conn := meta.(*AWSClient).ec2conn
   270  
   271  	// Update tags if required.
   272  	if err := setTagsSDK(conn, d); err != nil {
   273  		return err
   274  	}
   275  
   276  	d.SetPartial("tags")
   277  
   278  	return resourceAwsVpnConnectionRead(d, meta)
   279  }
   280  
   281  func resourceAwsVpnConnectionDelete(d *schema.ResourceData, meta interface{}) error {
   282  	conn := meta.(*AWSClient).ec2conn
   283  
   284  	_, err := conn.DeleteVPNConnection(&ec2.DeleteVPNConnectionInput{
   285  		VPNConnectionID: aws.String(d.Id()),
   286  	})
   287  	if err != nil {
   288  		if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnConnectionID.NotFound" {
   289  			d.SetId("")
   290  			return nil
   291  		} else {
   292  			log.Printf("[ERROR] Error deleting VPN connection: %s", err)
   293  			return err
   294  		}
   295  	}
   296  
   297  	// These things can take quite a while to tear themselves down and any
   298  	// attempt to modify resources they reference (e.g. CustomerGateways or
   299  	// VPN Gateways) before deletion will result in an error. Furthermore,
   300  	// they don't just disappear. The go into "deleted" state. We need to
   301  	// wait to ensure any other modifications the user might make to their
   302  	// VPC stack can safely run.
   303  	stateConf := &resource.StateChangeConf{
   304  		Pending:    []string{"deleting"},
   305  		Target:     "deleted",
   306  		Refresh:    vpnConnectionRefreshFunc(conn, d.Id()),
   307  		Timeout:    30 * time.Minute,
   308  		Delay:      10 * time.Second,
   309  		MinTimeout: 10 * time.Second,
   310  	}
   311  
   312  	_, stateErr := stateConf.WaitForState()
   313  	if stateErr != nil {
   314  		return fmt.Errorf(
   315  			"Error waiting for VPN connection (%s) to delete: %s", d.Id(), err)
   316  	}
   317  
   318  	return nil
   319  }
   320  
   321  // routesToMapList turns the list of routes into a list of maps.
   322  func routesToMapList(routes []*ec2.VPNStaticRoute) []map[string]interface{} {
   323  	result := make([]map[string]interface{}, 0, len(routes))
   324  	for _, r := range routes {
   325  		staticRoute := make(map[string]interface{})
   326  		staticRoute["destination_cidr_block"] = *r.DestinationCIDRBlock
   327  		staticRoute["state"] = *r.State
   328  
   329  		if r.Source != nil {
   330  			staticRoute["source"] = *r.Source
   331  		}
   332  
   333  		result = append(result, staticRoute)
   334  	}
   335  
   336  	return result
   337  }
   338  
   339  // telemetryToMapList turns the VGW telemetry into a list of maps.
   340  func telemetryToMapList(telemetry []*ec2.VGWTelemetry) []map[string]interface{} {
   341  	result := make([]map[string]interface{}, 0, len(telemetry))
   342  	for _, t := range telemetry {
   343  		vgw := make(map[string]interface{})
   344  		vgw["accepted_route_count"] = *t.AcceptedRouteCount
   345  		vgw["outside_ip_address"] = *t.OutsideIPAddress
   346  		vgw["status"] = *t.Status
   347  		vgw["status_message"] = *t.StatusMessage
   348  
   349  		// LastStatusChange is a time.Time(). Convert it into a string
   350  		// so it can be handled by schema's type system.
   351  		vgw["last_status_change"] = t.LastStatusChange.String()
   352  		result = append(result, vgw)
   353  	}
   354  
   355  	return result
   356  }