github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_internet_gateway.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/aws/awserr" 10 "github.com/aws/aws-sdk-go/service/ec2" 11 "github.com/hashicorp/errwrap" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 ) 15 16 func resourceAwsInternetGateway() *schema.Resource { 17 return &schema.Resource{ 18 Create: resourceAwsInternetGatewayCreate, 19 Read: resourceAwsInternetGatewayRead, 20 Update: resourceAwsInternetGatewayUpdate, 21 Delete: resourceAwsInternetGatewayDelete, 22 Importer: &schema.ResourceImporter{ 23 State: schema.ImportStatePassthrough, 24 }, 25 26 Schema: map[string]*schema.Schema{ 27 "vpc_id": &schema.Schema{ 28 Type: schema.TypeString, 29 Optional: true, 30 }, 31 "tags": tagsSchema(), 32 }, 33 } 34 } 35 36 func resourceAwsInternetGatewayCreate(d *schema.ResourceData, meta interface{}) error { 37 conn := meta.(*AWSClient).ec2conn 38 39 // Create the gateway 40 log.Printf("[DEBUG] Creating internet gateway") 41 var err error 42 resp, err := conn.CreateInternetGateway(nil) 43 if err != nil { 44 return fmt.Errorf("Error creating internet gateway: %s", err) 45 } 46 47 // Get the ID and store it 48 ig := *resp.InternetGateway 49 d.SetId(*ig.InternetGatewayId) 50 log.Printf("[INFO] InternetGateway ID: %s", d.Id()) 51 52 err = resource.Retry(5*time.Minute, func() *resource.RetryError { 53 igRaw, _, err := IGStateRefreshFunc(conn, d.Id())() 54 if igRaw != nil { 55 return nil 56 } 57 if err == nil { 58 return resource.RetryableError(err) 59 } else { 60 return resource.NonRetryableError(err) 61 } 62 }) 63 64 if err != nil { 65 return errwrap.Wrapf("{{err}}", err) 66 } 67 68 err = setTags(conn, d) 69 if err != nil { 70 return err 71 } 72 73 // Attach the new gateway to the correct vpc 74 return resourceAwsInternetGatewayAttach(d, meta) 75 } 76 77 func resourceAwsInternetGatewayRead(d *schema.ResourceData, meta interface{}) error { 78 conn := meta.(*AWSClient).ec2conn 79 80 igRaw, _, err := IGStateRefreshFunc(conn, d.Id())() 81 if err != nil { 82 return err 83 } 84 if igRaw == nil { 85 // Seems we have lost our internet gateway 86 d.SetId("") 87 return nil 88 } 89 90 ig := igRaw.(*ec2.InternetGateway) 91 if len(ig.Attachments) == 0 { 92 // Gateway exists but not attached to the VPC 93 d.Set("vpc_id", "") 94 } else { 95 d.Set("vpc_id", ig.Attachments[0].VpcId) 96 } 97 98 d.Set("tags", tagsToMap(ig.Tags)) 99 100 return nil 101 } 102 103 func resourceAwsInternetGatewayUpdate(d *schema.ResourceData, meta interface{}) error { 104 if d.HasChange("vpc_id") { 105 // If we're already attached, detach it first 106 if err := resourceAwsInternetGatewayDetach(d, meta); err != nil { 107 return err 108 } 109 110 // Attach the gateway to the new vpc 111 if err := resourceAwsInternetGatewayAttach(d, meta); err != nil { 112 return err 113 } 114 } 115 116 conn := meta.(*AWSClient).ec2conn 117 118 if err := setTags(conn, d); err != nil { 119 return err 120 } 121 122 d.SetPartial("tags") 123 124 return nil 125 } 126 127 func resourceAwsInternetGatewayDelete(d *schema.ResourceData, meta interface{}) error { 128 conn := meta.(*AWSClient).ec2conn 129 130 // Detach if it is attached 131 if err := resourceAwsInternetGatewayDetach(d, meta); err != nil { 132 return err 133 } 134 135 log.Printf("[INFO] Deleting Internet Gateway: %s", d.Id()) 136 137 return resource.Retry(5*time.Minute, func() *resource.RetryError { 138 _, err := conn.DeleteInternetGateway(&ec2.DeleteInternetGatewayInput{ 139 InternetGatewayId: aws.String(d.Id()), 140 }) 141 if err == nil { 142 return nil 143 } 144 145 ec2err, ok := err.(awserr.Error) 146 if !ok { 147 return resource.RetryableError(err) 148 } 149 150 switch ec2err.Code() { 151 case "InvalidInternetGatewayID.NotFound": 152 return nil 153 case "DependencyViolation": 154 return resource.RetryableError(err) // retry 155 } 156 157 return resource.NonRetryableError(err) 158 }) 159 } 160 161 func resourceAwsInternetGatewayAttach(d *schema.ResourceData, meta interface{}) error { 162 conn := meta.(*AWSClient).ec2conn 163 164 if d.Get("vpc_id").(string) == "" { 165 log.Printf( 166 "[DEBUG] Not attaching Internet Gateway '%s' as no VPC ID is set", 167 d.Id()) 168 return nil 169 } 170 171 log.Printf( 172 "[INFO] Attaching Internet Gateway '%s' to VPC '%s'", 173 d.Id(), 174 d.Get("vpc_id").(string)) 175 176 err := resource.Retry(2*time.Minute, func() *resource.RetryError { 177 _, err := conn.AttachInternetGateway(&ec2.AttachInternetGatewayInput{ 178 InternetGatewayId: aws.String(d.Id()), 179 VpcId: aws.String(d.Get("vpc_id").(string)), 180 }) 181 if err == nil { 182 return nil 183 } 184 if ec2err, ok := err.(awserr.Error); ok { 185 switch ec2err.Code() { 186 case "InvalidInternetGatewayID.NotFound": 187 return resource.RetryableError(err) // retry 188 } 189 } 190 return resource.NonRetryableError(err) 191 }) 192 if err != nil { 193 return err 194 } 195 196 // A note on the states below: the AWS docs (as of July, 2014) say 197 // that the states would be: attached, attaching, detached, detaching, 198 // but when running, I noticed that the state is usually "available" when 199 // it is attached. 200 201 // Wait for it to be fully attached before continuing 202 log.Printf("[DEBUG] Waiting for internet gateway (%s) to attach", d.Id()) 203 stateConf := &resource.StateChangeConf{ 204 Pending: []string{"detached", "attaching"}, 205 Target: []string{"available"}, 206 Refresh: IGAttachStateRefreshFunc(conn, d.Id(), "available"), 207 Timeout: 4 * time.Minute, 208 } 209 if _, err := stateConf.WaitForState(); err != nil { 210 return fmt.Errorf( 211 "Error waiting for internet gateway (%s) to attach: %s", 212 d.Id(), err) 213 } 214 215 return nil 216 } 217 218 func resourceAwsInternetGatewayDetach(d *schema.ResourceData, meta interface{}) error { 219 conn := meta.(*AWSClient).ec2conn 220 221 // Get the old VPC ID to detach from 222 vpcID, _ := d.GetChange("vpc_id") 223 224 if vpcID.(string) == "" { 225 log.Printf( 226 "[DEBUG] Not detaching Internet Gateway '%s' as no VPC ID is set", 227 d.Id()) 228 return nil 229 } 230 231 log.Printf( 232 "[INFO] Detaching Internet Gateway '%s' from VPC '%s'", 233 d.Id(), 234 vpcID.(string)) 235 236 // Wait for it to be fully detached before continuing 237 log.Printf("[DEBUG] Waiting for internet gateway (%s) to detach", d.Id()) 238 stateConf := &resource.StateChangeConf{ 239 Pending: []string{"detaching"}, 240 Target: []string{"detached"}, 241 Refresh: detachIGStateRefreshFunc(conn, d.Id(), vpcID.(string)), 242 Timeout: 15 * time.Minute, 243 Delay: 10 * time.Second, 244 NotFoundChecks: 30, 245 } 246 if _, err := stateConf.WaitForState(); err != nil { 247 return fmt.Errorf( 248 "Error waiting for internet gateway (%s) to detach: %s", 249 d.Id(), err) 250 } 251 252 return nil 253 } 254 255 // InstanceStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 256 // an EC2 instance. 257 func detachIGStateRefreshFunc(conn *ec2.EC2, gatewayID, vpcID string) resource.StateRefreshFunc { 258 return func() (interface{}, string, error) { 259 _, err := conn.DetachInternetGateway(&ec2.DetachInternetGatewayInput{ 260 InternetGatewayId: aws.String(gatewayID), 261 VpcId: aws.String(vpcID), 262 }) 263 if err != nil { 264 if ec2err, ok := err.(awserr.Error); ok { 265 switch ec2err.Code() { 266 case "InvalidInternetGatewayID.NotFound": 267 log.Printf("[TRACE] Error detaching Internet Gateway '%s' from VPC '%s': %s", gatewayID, vpcID, err) 268 return nil, "Not Found", nil 269 270 case "Gateway.NotAttached": 271 return "detached", "detached", nil 272 273 case "DependencyViolation": 274 return nil, "detaching", nil 275 } 276 } 277 } 278 279 // DetachInternetGateway only returns an error, so if it's nil, assume we're 280 // detached 281 return "detached", "detached", nil 282 } 283 } 284 285 // IGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 286 // an internet gateway. 287 func IGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 288 return func() (interface{}, string, error) { 289 resp, err := conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysInput{ 290 InternetGatewayIds: []*string{aws.String(id)}, 291 }) 292 if err != nil { 293 ec2err, ok := err.(awserr.Error) 294 if ok && ec2err.Code() == "InvalidInternetGatewayID.NotFound" { 295 resp = nil 296 } else { 297 log.Printf("[ERROR] Error on IGStateRefresh: %s", err) 298 return nil, "", err 299 } 300 } 301 302 if resp == nil { 303 // Sometimes AWS just has consistency issues and doesn't see 304 // our instance yet. Return an empty state. 305 return nil, "", nil 306 } 307 308 ig := resp.InternetGateways[0] 309 return ig, "available", nil 310 } 311 } 312 313 // IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used 314 // watch the state of an internet gateway's attachment. 315 func IGAttachStateRefreshFunc(conn *ec2.EC2, id string, expected string) resource.StateRefreshFunc { 316 var start time.Time 317 return func() (interface{}, string, error) { 318 if start.IsZero() { 319 start = time.Now() 320 } 321 322 resp, err := conn.DescribeInternetGateways(&ec2.DescribeInternetGatewaysInput{ 323 InternetGatewayIds: []*string{aws.String(id)}, 324 }) 325 if err != nil { 326 ec2err, ok := err.(awserr.Error) 327 if ok && ec2err.Code() == "InvalidInternetGatewayID.NotFound" { 328 resp = nil 329 } else { 330 log.Printf("[ERROR] Error on IGStateRefresh: %s", err) 331 return nil, "", err 332 } 333 } 334 335 if resp == nil { 336 // Sometimes AWS just has consistency issues and doesn't see 337 // our instance yet. Return an empty state. 338 return nil, "", nil 339 } 340 341 ig := resp.InternetGateways[0] 342 343 if time.Now().Sub(start) > 10*time.Second { 344 return ig, expected, nil 345 } 346 347 if len(ig.Attachments) == 0 { 348 // No attachments, we're detached 349 return ig, "detached", nil 350 } 351 352 return ig, *ig.Attachments[0].State, nil 353 } 354 }