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