github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/builtin/providers/aws/resource_aws_vpn_gateway.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/hashicorp/aws-sdk-go/aws"
     9  	"github.com/hashicorp/aws-sdk-go/gen/ec2"
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  )
    13  
    14  func resourceAwsVpnGateway() *schema.Resource {
    15  	return &schema.Resource{
    16  		Create: resourceAwsVpnGatewayCreate,
    17  		Read:   resourceAwsVpnGatewayRead,
    18  		Update: resourceAwsVpnGatewayUpdate,
    19  		Delete: resourceAwsVpnGatewayDelete,
    20  
    21  		Schema: map[string]*schema.Schema{
    22  			"availability_zone": &schema.Schema{
    23  				Type:     schema.TypeString,
    24  				Optional: true,
    25  				ForceNew: true,
    26  			},
    27  
    28  			"vpc_id": &schema.Schema{
    29  				Type:     schema.TypeString,
    30  				Optional: true,
    31  			},
    32  
    33  			"tags": tagsSchema(),
    34  		},
    35  	}
    36  }
    37  
    38  func resourceAwsVpnGatewayCreate(d *schema.ResourceData, meta interface{}) error {
    39  	ec2conn := meta.(*AWSClient).ec2conn
    40  
    41  	createOpts := &ec2.CreateVPNGatewayRequest{
    42  		AvailabilityZone: aws.String(d.Get("availability_zone").(string)),
    43  		Type:             aws.String("ipsec.1"),
    44  	}
    45  
    46  	// Create the VPN gateway
    47  	log.Printf("[DEBUG] Creating VPN gateway")
    48  	resp, err := ec2conn.CreateVPNGateway(createOpts)
    49  	if err != nil {
    50  		return fmt.Errorf("Error creating VPN gateway: %s", err)
    51  	}
    52  
    53  	// Get the ID and store it
    54  	vpnGateway := resp.VPNGateway
    55  	d.SetId(*vpnGateway.VPNGatewayID)
    56  	log.Printf("[INFO] VPN Gateway ID: %s", *vpnGateway.VPNGatewayID)
    57  
    58  	// Attach the VPN gateway to the correct VPC
    59  	return resourceAwsVpnGatewayUpdate(d, meta)
    60  }
    61  
    62  func resourceAwsVpnGatewayRead(d *schema.ResourceData, meta interface{}) error {
    63  	ec2conn := meta.(*AWSClient).ec2conn
    64  
    65  	vpnGatewayRaw, _, err := vpnGatewayStateRefreshFunc(ec2conn, d.Id())()
    66  	if err != nil {
    67  		return err
    68  	}
    69  	if vpnGatewayRaw == nil {
    70  		// Seems we have lost our VPN gateway
    71  		d.SetId("")
    72  		return nil
    73  	}
    74  
    75  	vpnGateway := vpnGatewayRaw.(*ec2.VPNGateway)
    76  	if len(vpnGateway.VPCAttachments) == 0 {
    77  		// Gateway exists but not attached to the VPC
    78  		d.Set("vpc_id", "")
    79  	} else {
    80  		d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID)
    81  	}
    82  	d.Set("availability_zone", vpnGateway.AvailabilityZone)
    83  	d.Set("tags", tagsToMap(vpnGateway.Tags))
    84  
    85  	return nil
    86  }
    87  
    88  func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error {
    89  	if d.HasChange("vpc_id") {
    90  		// If we're already attached, detach it first
    91  		if err := resourceAwsVpnGatewayDetach(d, meta); err != nil {
    92  			return err
    93  		}
    94  
    95  		// Attach the VPN gateway to the new vpc
    96  		if err := resourceAwsVpnGatewayAttach(d, meta); err != nil {
    97  			return err
    98  		}
    99  	}
   100  
   101  	ec2conn := meta.(*AWSClient).ec2conn
   102  
   103  	if err := setTags(ec2conn, d); err != nil {
   104  		return err
   105  	}
   106  
   107  	d.SetPartial("tags")
   108  
   109  	return resourceAwsVpnGatewayRead(d, meta)
   110  }
   111  
   112  func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error {
   113  	ec2conn := meta.(*AWSClient).ec2conn
   114  
   115  	// Detach if it is attached
   116  	if err := resourceAwsVpnGatewayDetach(d, meta); err != nil {
   117  		return err
   118  	}
   119  
   120  	log.Printf("[INFO] Deleting VPN gateway: %s", d.Id())
   121  
   122  	return resource.Retry(5*time.Minute, func() error {
   123  		err := ec2conn.DeleteVPNGateway(&ec2.DeleteVPNGatewayRequest{
   124  			VPNGatewayID: aws.String(d.Id()),
   125  		})
   126  		if err == nil {
   127  			return nil
   128  		}
   129  
   130  		ec2err, ok := err.(aws.APIError)
   131  		if !ok {
   132  			return err
   133  		}
   134  
   135  		switch ec2err.Code {
   136  		case "InvalidVpnGatewayID.NotFound":
   137  			return nil
   138  		case "IncorrectState":
   139  			return err // retry
   140  		}
   141  
   142  		return resource.RetryError{Err: err}
   143  	})
   144  }
   145  
   146  func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error {
   147  	ec2conn := meta.(*AWSClient).ec2conn
   148  
   149  	if d.Get("vpc_id").(string) == "" {
   150  		log.Printf(
   151  			"[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set",
   152  			d.Id())
   153  		return nil
   154  	}
   155  
   156  	log.Printf(
   157  		"[INFO] Attaching VPN Gateway '%s' to VPC '%s'",
   158  		d.Id(),
   159  		d.Get("vpc_id").(string))
   160  
   161  	_, err := ec2conn.AttachVPNGateway(&ec2.AttachVPNGatewayRequest{
   162  		VPNGatewayID: aws.String(d.Id()),
   163  		VPCID:        aws.String(d.Get("vpc_id").(string)),
   164  	})
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	// A note on the states below: the AWS docs (as of July, 2014) say
   170  	// that the states would be: attached, attaching, detached, detaching,
   171  	// but when running, I noticed that the state is usually "available" when
   172  	// it is attached.
   173  
   174  	// Wait for it to be fully attached before continuing
   175  	log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id())
   176  	stateConf := &resource.StateChangeConf{
   177  		Pending: []string{"detached", "attaching"},
   178  		Target:  "available",
   179  		Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "available"),
   180  		Timeout: 1 * time.Minute,
   181  	}
   182  	if _, err := stateConf.WaitForState(); err != nil {
   183  		return fmt.Errorf(
   184  			"Error waiting for VPN gateway (%s) to attach: %s",
   185  			d.Id(), err)
   186  	}
   187  
   188  	return nil
   189  }
   190  
   191  func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error {
   192  	ec2conn := meta.(*AWSClient).ec2conn
   193  
   194  	// Get the old VPC ID to detach from
   195  	vpcID, _ := d.GetChange("vpc_id")
   196  
   197  	if vpcID.(string) == "" {
   198  		log.Printf(
   199  			"[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set",
   200  			d.Id())
   201  		return nil
   202  	}
   203  
   204  	log.Printf(
   205  		"[INFO] Detaching VPN Gateway '%s' from VPC '%s'",
   206  		d.Id(),
   207  		vpcID.(string))
   208  
   209  	wait := true
   210  	err := ec2conn.DetachVPNGateway(&ec2.DetachVPNGatewayRequest{
   211  		VPNGatewayID: aws.String(d.Id()),
   212  		VPCID:        aws.String(d.Get("vpc_id").(string)),
   213  	})
   214  	if err != nil {
   215  		ec2err, ok := err.(aws.APIError)
   216  		if ok {
   217  			if ec2err.Code == "InvalidVpnGatewayID.NotFound" {
   218  				err = nil
   219  				wait = false
   220  			} else if ec2err.Code == "InvalidVpnGatewayAttachment.NotFound" {
   221  				err = nil
   222  				wait = false
   223  			}
   224  		}
   225  
   226  		if err != nil {
   227  			return err
   228  		}
   229  	}
   230  
   231  	if !wait {
   232  		return nil
   233  	}
   234  
   235  	// Wait for it to be fully detached before continuing
   236  	log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id())
   237  	stateConf := &resource.StateChangeConf{
   238  		Pending: []string{"attached", "detaching", "available"},
   239  		Target:  "detached",
   240  		Refresh: VpnGatewayAttachStateRefreshFunc(ec2conn, d.Id(), "detached"),
   241  		Timeout: 1 * time.Minute,
   242  	}
   243  	if _, err := stateConf.WaitForState(); err != nil {
   244  		return fmt.Errorf(
   245  			"Error waiting for vpn gateway (%s) to detach: %s",
   246  			d.Id(), err)
   247  	}
   248  
   249  	return nil
   250  }
   251  
   252  // vpnGatewayStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch a VPNGateway.
   253  func vpnGatewayStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   254  	return func() (interface{}, string, error) {
   255  		resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{
   256  			VPNGatewayIDs: []string{id},
   257  		})
   258  		if err != nil {
   259  			if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" {
   260  				resp = nil
   261  			} else {
   262  				log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err)
   263  				return nil, "", err
   264  			}
   265  		}
   266  
   267  		if resp == nil {
   268  			// Sometimes AWS just has consistency issues and doesn't see
   269  			// our instance yet. Return an empty state.
   270  			return nil, "", nil
   271  		}
   272  
   273  		vpnGateway := &resp.VPNGateways[0]
   274  		return vpnGateway, *vpnGateway.State, nil
   275  	}
   276  }
   277  
   278  // VpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   279  // the state of a VPN gateway's attachment
   280  func VpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc {
   281  	var start time.Time
   282  	return func() (interface{}, string, error) {
   283  		if start.IsZero() {
   284  			start = time.Now()
   285  		}
   286  
   287  		resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysRequest{
   288  			VPNGatewayIDs: []string{id},
   289  		})
   290  		if err != nil {
   291  			if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" {
   292  				resp = nil
   293  			} else {
   294  				log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err)
   295  				return nil, "", err
   296  			}
   297  		}
   298  
   299  		if resp == nil {
   300  			// Sometimes AWS just has consistency issues and doesn't see
   301  			// our instance yet. Return an empty state.
   302  			return nil, "", nil
   303  		}
   304  
   305  		vpnGateway := &resp.VPNGateways[0]
   306  
   307  		if time.Now().Sub(start) > 10*time.Second {
   308  			return vpnGateway, expected, nil
   309  		}
   310  
   311  		if len(vpnGateway.VPCAttachments) == 0 {
   312  			// No attachments, we're detached
   313  			return vpnGateway, "detached", nil
   314  		}
   315  
   316  		return vpnGateway, *vpnGateway.VPCAttachments[0].State, nil
   317  	}
   318  }