github.com/nicgrayson/terraform@v0.4.3-0.20150415203910-c4de50829380/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/awslabs/aws-sdk-go/aws"
     9  	"github.com/awslabs/aws-sdk-go/service/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  	conn := meta.(*AWSClient).ec2SDKconn
    40  
    41  	createOpts := &ec2.CreateVPNGatewayInput{
    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 := conn.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  	conn := meta.(*AWSClient).ec2SDKconn
    64  
    65  	resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysInput{
    66  		VPNGatewayIDs: []*string{aws.String(d.Id())},
    67  	})
    68  	if err != nil {
    69  		if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" {
    70  			d.SetId("")
    71  			return nil
    72  		} else {
    73  			log.Printf("[ERROR] Error finding VpnGateway: %s", err)
    74  			return err
    75  		}
    76  	}
    77  
    78  	vpnGateway := resp.VPNGateways[0]
    79  	if vpnGateway == nil {
    80  		// Seems we have lost our VPN gateway
    81  		d.SetId("")
    82  		return nil
    83  	}
    84  
    85  	if len(vpnGateway.VPCAttachments) == 0 {
    86  		// Gateway exists but not attached to the VPC
    87  		d.Set("vpc_id", "")
    88  	} else {
    89  		d.Set("vpc_id", vpnGateway.VPCAttachments[0].VPCID)
    90  	}
    91  	d.Set("availability_zone", vpnGateway.AvailabilityZone)
    92  	d.Set("tags", tagsToMapSDK(vpnGateway.Tags))
    93  
    94  	return nil
    95  }
    96  
    97  func resourceAwsVpnGatewayUpdate(d *schema.ResourceData, meta interface{}) error {
    98  	if d.HasChange("vpc_id") {
    99  		// If we're already attached, detach it first
   100  		if err := resourceAwsVpnGatewayDetach(d, meta); err != nil {
   101  			return err
   102  		}
   103  
   104  		// Attach the VPN gateway to the new vpc
   105  		if err := resourceAwsVpnGatewayAttach(d, meta); err != nil {
   106  			return err
   107  		}
   108  	}
   109  
   110  	conn := meta.(*AWSClient).ec2SDKconn
   111  
   112  	if err := setTagsSDK(conn, d); err != nil {
   113  		return err
   114  	}
   115  
   116  	d.SetPartial("tags")
   117  
   118  	return resourceAwsVpnGatewayRead(d, meta)
   119  }
   120  
   121  func resourceAwsVpnGatewayDelete(d *schema.ResourceData, meta interface{}) error {
   122  	conn := meta.(*AWSClient).ec2SDKconn
   123  
   124  	// Detach if it is attached
   125  	if err := resourceAwsVpnGatewayDetach(d, meta); err != nil {
   126  		return err
   127  	}
   128  
   129  	log.Printf("[INFO] Deleting VPN gateway: %s", d.Id())
   130  
   131  	return resource.Retry(5*time.Minute, func() error {
   132  		_, err := conn.DeleteVPNGateway(&ec2.DeleteVPNGatewayInput{
   133  			VPNGatewayID: aws.String(d.Id()),
   134  		})
   135  		if err == nil {
   136  			return nil
   137  		}
   138  
   139  		ec2err, ok := err.(aws.APIError)
   140  		if !ok {
   141  			return err
   142  		}
   143  
   144  		switch ec2err.Code {
   145  		case "InvalidVpnGatewayID.NotFound":
   146  			return nil
   147  		case "IncorrectState":
   148  			return err // retry
   149  		}
   150  
   151  		return resource.RetryError{Err: err}
   152  	})
   153  }
   154  
   155  func resourceAwsVpnGatewayAttach(d *schema.ResourceData, meta interface{}) error {
   156  	conn := meta.(*AWSClient).ec2SDKconn
   157  
   158  	if d.Get("vpc_id").(string) == "" {
   159  		log.Printf(
   160  			"[DEBUG] Not attaching VPN Gateway '%s' as no VPC ID is set",
   161  			d.Id())
   162  		return nil
   163  	}
   164  
   165  	log.Printf(
   166  		"[INFO] Attaching VPN Gateway '%s' to VPC '%s'",
   167  		d.Id(),
   168  		d.Get("vpc_id").(string))
   169  
   170  	_, err := conn.AttachVPNGateway(&ec2.AttachVPNGatewayInput{
   171  		VPNGatewayID: aws.String(d.Id()),
   172  		VPCID:        aws.String(d.Get("vpc_id").(string)),
   173  	})
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	// A note on the states below: the AWS docs (as of July, 2014) say
   179  	// that the states would be: attached, attaching, detached, detaching,
   180  	// but when running, I noticed that the state is usually "available" when
   181  	// it is attached.
   182  
   183  	// Wait for it to be fully attached before continuing
   184  	log.Printf("[DEBUG] Waiting for VPN gateway (%s) to attach", d.Id())
   185  	stateConf := &resource.StateChangeConf{
   186  		Pending: []string{"detached", "attaching"},
   187  		Target:  "available",
   188  		Refresh: vpnGatewayAttachStateRefreshFunc(conn, d.Id(), "available"),
   189  		Timeout: 1 * time.Minute,
   190  	}
   191  	if _, err := stateConf.WaitForState(); err != nil {
   192  		return fmt.Errorf(
   193  			"Error waiting for VPN gateway (%s) to attach: %s",
   194  			d.Id(), err)
   195  	}
   196  
   197  	return nil
   198  }
   199  
   200  func resourceAwsVpnGatewayDetach(d *schema.ResourceData, meta interface{}) error {
   201  	conn := meta.(*AWSClient).ec2SDKconn
   202  
   203  	// Get the old VPC ID to detach from
   204  	vpcID, _ := d.GetChange("vpc_id")
   205  
   206  	if vpcID.(string) == "" {
   207  		log.Printf(
   208  			"[DEBUG] Not detaching VPN Gateway '%s' as no VPC ID is set",
   209  			d.Id())
   210  		return nil
   211  	}
   212  
   213  	log.Printf(
   214  		"[INFO] Detaching VPN Gateway '%s' from VPC '%s'",
   215  		d.Id(),
   216  		vpcID.(string))
   217  
   218  	wait := true
   219  	_, err := conn.DetachVPNGateway(&ec2.DetachVPNGatewayInput{
   220  		VPNGatewayID: aws.String(d.Id()),
   221  		VPCID:        aws.String(d.Get("vpc_id").(string)),
   222  	})
   223  	if err != nil {
   224  		ec2err, ok := err.(aws.APIError)
   225  		if ok {
   226  			if ec2err.Code == "InvalidVpnGatewayID.NotFound" {
   227  				err = nil
   228  				wait = false
   229  			} else if ec2err.Code == "InvalidVpnGatewayAttachment.NotFound" {
   230  				err = nil
   231  				wait = false
   232  			}
   233  		}
   234  
   235  		if err != nil {
   236  			return err
   237  		}
   238  	}
   239  
   240  	if !wait {
   241  		return nil
   242  	}
   243  
   244  	// Wait for it to be fully detached before continuing
   245  	log.Printf("[DEBUG] Waiting for VPN gateway (%s) to detach", d.Id())
   246  	stateConf := &resource.StateChangeConf{
   247  		Pending: []string{"attached", "detaching", "available"},
   248  		Target:  "detached",
   249  		Refresh: vpnGatewayAttachStateRefreshFunc(conn, d.Id(), "detached"),
   250  		Timeout: 1 * time.Minute,
   251  	}
   252  	if _, err := stateConf.WaitForState(); err != nil {
   253  		return fmt.Errorf(
   254  			"Error waiting for vpn gateway (%s) to detach: %s",
   255  			d.Id(), err)
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  // vpnGatewayAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
   262  // the state of a VPN gateway's attachment
   263  func vpnGatewayAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc {
   264  	var start time.Time
   265  	return func() (interface{}, string, error) {
   266  		if start.IsZero() {
   267  			start = time.Now()
   268  		}
   269  
   270  		resp, err := conn.DescribeVPNGateways(&ec2.DescribeVPNGatewaysInput{
   271  			VPNGatewayIDs: []*string{aws.String(id)},
   272  		})
   273  		if err != nil {
   274  			if ec2err, ok := err.(aws.APIError); ok && ec2err.Code == "InvalidVpnGatewayID.NotFound" {
   275  				resp = nil
   276  			} else {
   277  				log.Printf("[ERROR] Error on VpnGatewayStateRefresh: %s", err)
   278  				return nil, "", err
   279  			}
   280  		}
   281  
   282  		if resp == nil {
   283  			// Sometimes AWS just has consistency issues and doesn't see
   284  			// our instance yet. Return an empty state.
   285  			return nil, "", nil
   286  		}
   287  
   288  		vpnGateway := resp.VPNGateways[0]
   289  
   290  		if time.Now().Sub(start) > 10*time.Second {
   291  			return vpnGateway, expected, nil
   292  		}
   293  
   294  		if len(vpnGateway.VPCAttachments) == 0 {
   295  			// No attachments, we're detached
   296  			return vpnGateway, "detached", nil
   297  		}
   298  
   299  		return vpnGateway, *vpnGateway.VPCAttachments[0].State, nil
   300  	}
   301  }