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 }