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