github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/builtin/providers/aws/resource_aws_security_group.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "sort" 8 "time" 9 10 "github.com/hashicorp/aws-sdk-go/aws" 11 "github.com/hashicorp/aws-sdk-go/gen/ec2" 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 15 ) 16 17 func resourceAwsSecurityGroup() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceAwsSecurityGroupCreate, 20 Read: resourceAwsSecurityGroupRead, 21 Update: resourceAwsSecurityGroupUpdate, 22 Delete: resourceAwsSecurityGroupDelete, 23 24 Schema: map[string]*schema.Schema{ 25 "name": &schema.Schema{ 26 Type: schema.TypeString, 27 Required: true, 28 ForceNew: true, 29 }, 30 31 "description": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 }, 35 36 "vpc_id": &schema.Schema{ 37 Type: schema.TypeString, 38 Optional: true, 39 ForceNew: true, 40 Computed: true, 41 }, 42 43 "ingress": &schema.Schema{ 44 Type: schema.TypeSet, 45 Optional: true, 46 Elem: &schema.Resource{ 47 Schema: map[string]*schema.Schema{ 48 "from_port": &schema.Schema{ 49 Type: schema.TypeInt, 50 Required: true, 51 }, 52 53 "to_port": &schema.Schema{ 54 Type: schema.TypeInt, 55 Required: true, 56 }, 57 58 "protocol": &schema.Schema{ 59 Type: schema.TypeString, 60 Required: true, 61 }, 62 63 "cidr_blocks": &schema.Schema{ 64 Type: schema.TypeList, 65 Optional: true, 66 Elem: &schema.Schema{Type: schema.TypeString}, 67 }, 68 69 "security_groups": &schema.Schema{ 70 Type: schema.TypeSet, 71 Optional: true, 72 Elem: &schema.Schema{Type: schema.TypeString}, 73 Set: func(v interface{}) int { 74 return hashcode.String(v.(string)) 75 }, 76 }, 77 78 "self": &schema.Schema{ 79 Type: schema.TypeBool, 80 Optional: true, 81 Default: false, 82 }, 83 }, 84 }, 85 Set: resourceAwsSecurityGroupRuleHash, 86 }, 87 88 "egress": &schema.Schema{ 89 Type: schema.TypeSet, 90 Optional: true, 91 Computed: true, 92 Elem: &schema.Resource{ 93 Schema: map[string]*schema.Schema{ 94 "from_port": &schema.Schema{ 95 Type: schema.TypeInt, 96 Required: true, 97 }, 98 99 "to_port": &schema.Schema{ 100 Type: schema.TypeInt, 101 Required: true, 102 }, 103 104 "protocol": &schema.Schema{ 105 Type: schema.TypeString, 106 Required: true, 107 }, 108 109 "cidr_blocks": &schema.Schema{ 110 Type: schema.TypeList, 111 Optional: true, 112 Elem: &schema.Schema{Type: schema.TypeString}, 113 }, 114 115 "security_groups": &schema.Schema{ 116 Type: schema.TypeSet, 117 Optional: true, 118 Elem: &schema.Schema{Type: schema.TypeString}, 119 Set: func(v interface{}) int { 120 return hashcode.String(v.(string)) 121 }, 122 }, 123 124 "self": &schema.Schema{ 125 Type: schema.TypeBool, 126 Optional: true, 127 Default: false, 128 }, 129 }, 130 }, 131 Set: resourceAwsSecurityGroupRuleHash, 132 }, 133 134 "owner_id": &schema.Schema{ 135 Type: schema.TypeString, 136 Computed: true, 137 }, 138 139 "tags": tagsSchema(), 140 }, 141 } 142 } 143 144 func resourceAwsSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { 145 ec2conn := meta.(*AWSClient).ec2conn 146 147 securityGroupOpts := &ec2.CreateSecurityGroupRequest{ 148 GroupName: aws.String(d.Get("name").(string)), 149 } 150 151 if v := d.Get("vpc_id"); v != nil { 152 securityGroupOpts.VPCID = aws.String(v.(string)) 153 } 154 155 if v := d.Get("description"); v != nil { 156 securityGroupOpts.Description = aws.String(v.(string)) 157 } 158 159 log.Printf( 160 "[DEBUG] Security Group create configuration: %#v", securityGroupOpts) 161 createResp, err := ec2conn.CreateSecurityGroup(securityGroupOpts) 162 if err != nil { 163 return fmt.Errorf("Error creating Security Group: %s", err) 164 } 165 166 d.SetId(*createResp.GroupID) 167 168 log.Printf("[INFO] Security Group ID: %s", d.Id()) 169 170 // Wait for the security group to truly exist 171 log.Printf( 172 "[DEBUG] Waiting for Security Group (%s) to exist", 173 d.Id()) 174 stateConf := &resource.StateChangeConf{ 175 Pending: []string{""}, 176 Target: "exists", 177 Refresh: SGStateRefreshFunc(ec2conn, d.Id()), 178 Timeout: 1 * time.Minute, 179 } 180 if _, err := stateConf.WaitForState(); err != nil { 181 return fmt.Errorf( 182 "Error waiting for Security Group (%s) to become available: %s", 183 d.Id(), err) 184 } 185 186 return resourceAwsSecurityGroupUpdate(d, meta) 187 } 188 189 func resourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { 190 ec2conn := meta.(*AWSClient).ec2conn 191 192 sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())() 193 if err != nil { 194 return err 195 } 196 if sgRaw == nil { 197 d.SetId("") 198 return nil 199 } 200 201 sg := sgRaw.(ec2.SecurityGroup) 202 203 ingressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissions) 204 egressRules := resourceAwsSecurityGroupIPPermGather(d, sg.IPPermissionsEgress) 205 206 d.Set("description", sg.Description) 207 d.Set("name", sg.GroupName) 208 d.Set("vpc_id", sg.VPCID) 209 d.Set("owner_id", sg.OwnerID) 210 d.Set("ingress", ingressRules) 211 d.Set("egress", egressRules) 212 d.Set("tags", tagsToMap(sg.Tags)) 213 return nil 214 } 215 216 func resourceAwsSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { 217 ec2conn := meta.(*AWSClient).ec2conn 218 219 sgRaw, _, err := SGStateRefreshFunc(ec2conn, d.Id())() 220 if err != nil { 221 return err 222 } 223 if sgRaw == nil { 224 d.SetId("") 225 return nil 226 } 227 228 group := sgRaw.(ec2.SecurityGroup) 229 230 err = resourceAwsSecurityGroupUpdateRules(d, "ingress", meta, group) 231 if err != nil { 232 return err 233 } 234 235 if d.Get("vpc_id") != nil { 236 err = resourceAwsSecurityGroupUpdateRules(d, "egress", meta, group) 237 if err != nil { 238 return err 239 } 240 } 241 242 if err := setTags(ec2conn, d); err != nil { 243 return err 244 } 245 246 d.SetPartial("tags") 247 248 return resourceAwsSecurityGroupRead(d, meta) 249 } 250 251 func resourceAwsSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { 252 ec2conn := meta.(*AWSClient).ec2conn 253 254 log.Printf("[DEBUG] Security Group destroy: %v", d.Id()) 255 256 return resource.Retry(5*time.Minute, func() error { 257 err := ec2conn.DeleteSecurityGroup(&ec2.DeleteSecurityGroupRequest{ 258 GroupID: aws.String(d.Id()), 259 }) 260 if err != nil { 261 ec2err, ok := err.(aws.APIError) 262 if !ok { 263 return err 264 } 265 266 switch ec2err.Code { 267 case "InvalidGroup.NotFound": 268 return nil 269 case "DependencyViolation": 270 // If it is a dependency violation, we want to retry 271 return err 272 default: 273 // Any other error, we want to quit the retry loop immediately 274 return resource.RetryError{Err: err} 275 } 276 } 277 278 return nil 279 }) 280 } 281 282 func resourceAwsSecurityGroupRuleHash(v interface{}) int { 283 var buf bytes.Buffer 284 m := v.(map[string]interface{}) 285 buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int))) 286 buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int))) 287 buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) 288 buf.WriteString(fmt.Sprintf("%t-", m["self"].(bool))) 289 290 // We need to make sure to sort the strings below so that we always 291 // generate the same hash code no matter what is in the set. 292 if v, ok := m["cidr_blocks"]; ok { 293 vs := v.([]interface{}) 294 s := make([]string, len(vs)) 295 for i, raw := range vs { 296 s[i] = raw.(string) 297 } 298 sort.Strings(s) 299 300 for _, v := range s { 301 buf.WriteString(fmt.Sprintf("%s-", v)) 302 } 303 } 304 if v, ok := m["security_groups"]; ok { 305 vs := v.(*schema.Set).List() 306 s := make([]string, len(vs)) 307 for i, raw := range vs { 308 s[i] = raw.(string) 309 } 310 sort.Strings(s) 311 312 for _, v := range s { 313 buf.WriteString(fmt.Sprintf("%s-", v)) 314 } 315 } 316 317 return hashcode.String(buf.String()) 318 } 319 320 func resourceAwsSecurityGroupIPPermGather(d *schema.ResourceData, permissions []ec2.IPPermission) []map[string]interface{} { 321 ruleMap := make(map[string]map[string]interface{}) 322 for _, perm := range permissions { 323 var fromPort, toPort int 324 if v := perm.FromPort; v != nil { 325 fromPort = *v 326 } 327 if v := perm.ToPort; v != nil { 328 toPort = *v 329 } 330 331 k := fmt.Sprintf("%s-%d-%d", *perm.IPProtocol, fromPort, toPort) 332 m, ok := ruleMap[k] 333 if !ok { 334 m = make(map[string]interface{}) 335 ruleMap[k] = m 336 } 337 338 m["from_port"] = fromPort 339 m["to_port"] = toPort 340 m["protocol"] = *perm.IPProtocol 341 342 if len(perm.IPRanges) > 0 { 343 raw, ok := m["cidr_blocks"] 344 if !ok { 345 raw = make([]string, 0, len(perm.IPRanges)) 346 } 347 list := raw.([]string) 348 349 for _, ip := range perm.IPRanges { 350 list = append(list, *ip.CIDRIP) 351 } 352 353 m["cidr_blocks"] = list 354 } 355 356 var groups []string 357 if len(perm.UserIDGroupPairs) > 0 { 358 groups = flattenSecurityGroups(perm.UserIDGroupPairs) 359 } 360 for i, id := range groups { 361 if id == d.Id() { 362 groups[i], groups = groups[len(groups)-1], groups[:len(groups)-1] 363 m["self"] = true 364 } 365 } 366 367 if len(groups) > 0 { 368 raw, ok := m["security_groups"] 369 if !ok { 370 raw = make([]string, 0, len(groups)) 371 } 372 list := raw.([]string) 373 374 list = append(list, groups...) 375 m["security_groups"] = list 376 } 377 } 378 rules := make([]map[string]interface{}, 0, len(ruleMap)) 379 for _, m := range ruleMap { 380 rules = append(rules, m) 381 } 382 return rules 383 } 384 385 func resourceAwsSecurityGroupUpdateRules( 386 d *schema.ResourceData, ruleset string, 387 meta interface{}, group ec2.SecurityGroup) error { 388 if d.HasChange(ruleset) { 389 o, n := d.GetChange(ruleset) 390 if o == nil { 391 o = new(schema.Set) 392 } 393 if n == nil { 394 n = new(schema.Set) 395 } 396 397 os := o.(*schema.Set) 398 ns := n.(*schema.Set) 399 400 remove := expandIPPerms(group, os.Difference(ns).List()) 401 add := expandIPPerms(group, ns.Difference(os).List()) 402 403 // TODO: We need to handle partial state better in the in-between 404 // in this update. 405 406 // TODO: It'd be nicer to authorize before removing, but then we have 407 // to deal with complicated unrolling to get individual CIDR blocks 408 // to avoid authorizing already authorized sources. Removing before 409 // adding is easier here, and Terraform should be fast enough to 410 // not have service issues. 411 412 if len(remove) > 0 || len(add) > 0 { 413 ec2conn := meta.(*AWSClient).ec2conn 414 415 var err error 416 if len(remove) > 0 { 417 log.Printf("[DEBUG] Revoking security group %#v %s rule: %#v", 418 group, ruleset, remove) 419 420 if ruleset == "egress" { 421 req := &ec2.RevokeSecurityGroupEgressRequest{ 422 GroupID: group.GroupID, 423 IPPermissions: remove, 424 } 425 err = ec2conn.RevokeSecurityGroupEgress(req) 426 } else { 427 req := &ec2.RevokeSecurityGroupIngressRequest{ 428 GroupID: group.GroupID, 429 IPPermissions: remove, 430 } 431 err = ec2conn.RevokeSecurityGroupIngress(req) 432 } 433 434 if err != nil { 435 return fmt.Errorf( 436 "Error authorizing security group %s rules: %s", 437 ruleset, err) 438 } 439 } 440 441 if len(add) > 0 { 442 log.Printf("[DEBUG] Authorizing security group %#v %s rule: %#v", 443 group, ruleset, add) 444 // Authorize the new rules 445 if ruleset == "egress" { 446 req := &ec2.AuthorizeSecurityGroupEgressRequest{ 447 GroupID: group.GroupID, 448 IPPermissions: add, 449 } 450 err = ec2conn.AuthorizeSecurityGroupEgress(req) 451 } else { 452 req := &ec2.AuthorizeSecurityGroupIngressRequest{ 453 GroupID: group.GroupID, 454 IPPermissions: add, 455 } 456 if group.VPCID == nil || *group.VPCID == "" { 457 req.GroupID = nil 458 req.GroupName = group.GroupName 459 } 460 461 err = ec2conn.AuthorizeSecurityGroupIngress(req) 462 } 463 464 if err != nil { 465 return fmt.Errorf( 466 "Error authorizing security group %s rules: %s", 467 ruleset, err) 468 } 469 } 470 } 471 } 472 return nil 473 } 474 475 // SGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch 476 // a security group. 477 func SGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc { 478 return func() (interface{}, string, error) { 479 req := &ec2.DescribeSecurityGroupsRequest{ 480 GroupIDs: []string{id}, 481 } 482 resp, err := conn.DescribeSecurityGroups(req) 483 if err != nil { 484 if ec2err, ok := err.(aws.APIError); ok { 485 if ec2err.Code == "InvalidSecurityGroupID.NotFound" || 486 ec2err.Code == "InvalidGroup.NotFound" { 487 resp = nil 488 err = nil 489 } 490 } 491 492 if err != nil { 493 log.Printf("Error on SGStateRefresh: %s", err) 494 return nil, "", err 495 } 496 } 497 498 if resp == nil { 499 return nil, "", nil 500 } 501 502 group := resp.SecurityGroups[0] 503 return group, "exists", nil 504 } 505 }