github.com/boyvanduuren/terraform@v0.7.0-rc2.0.20160805175930-de822d909c40/builtin/providers/aws/resource_aws_db_security_group.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "strings" 8 "time" 9 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/awserr" 12 "github.com/aws/aws-sdk-go/service/iam" 13 "github.com/aws/aws-sdk-go/service/rds" 14 "github.com/hashicorp/go-multierror" 15 "github.com/hashicorp/terraform/helper/hashcode" 16 "github.com/hashicorp/terraform/helper/resource" 17 "github.com/hashicorp/terraform/helper/schema" 18 ) 19 20 func resourceAwsDbSecurityGroup() *schema.Resource { 21 return &schema.Resource{ 22 Create: resourceAwsDbSecurityGroupCreate, 23 Read: resourceAwsDbSecurityGroupRead, 24 Update: resourceAwsDbSecurityGroupUpdate, 25 Delete: resourceAwsDbSecurityGroupDelete, 26 Importer: &schema.ResourceImporter{ 27 State: schema.ImportStatePassthrough, 28 }, 29 30 Schema: map[string]*schema.Schema{ 31 "arn": &schema.Schema{ 32 Type: schema.TypeString, 33 Computed: true, 34 }, 35 36 "name": &schema.Schema{ 37 Type: schema.TypeString, 38 Required: true, 39 ForceNew: true, 40 }, 41 42 "description": &schema.Schema{ 43 Type: schema.TypeString, 44 Optional: true, 45 ForceNew: true, 46 Default: "Managed by Terraform", 47 }, 48 49 "ingress": &schema.Schema{ 50 Type: schema.TypeSet, 51 Required: true, 52 Elem: &schema.Resource{ 53 Schema: map[string]*schema.Schema{ 54 "cidr": &schema.Schema{ 55 Type: schema.TypeString, 56 Optional: true, 57 }, 58 59 "security_group_name": &schema.Schema{ 60 Type: schema.TypeString, 61 Optional: true, 62 Computed: true, 63 }, 64 65 "security_group_id": &schema.Schema{ 66 Type: schema.TypeString, 67 Optional: true, 68 Computed: true, 69 }, 70 71 "security_group_owner_id": &schema.Schema{ 72 Type: schema.TypeString, 73 Optional: true, 74 Computed: true, 75 }, 76 }, 77 }, 78 Set: resourceAwsDbSecurityGroupIngressHash, 79 }, 80 81 "tags": tagsSchema(), 82 }, 83 } 84 } 85 86 func resourceAwsDbSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { 87 conn := meta.(*AWSClient).rdsconn 88 tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) 89 90 var err error 91 var errs []error 92 93 opts := rds.CreateDBSecurityGroupInput{ 94 DBSecurityGroupName: aws.String(d.Get("name").(string)), 95 DBSecurityGroupDescription: aws.String(d.Get("description").(string)), 96 Tags: tags, 97 } 98 99 log.Printf("[DEBUG] DB Security Group create configuration: %#v", opts) 100 _, err = conn.CreateDBSecurityGroup(&opts) 101 if err != nil { 102 return fmt.Errorf("Error creating DB Security Group: %s", err) 103 } 104 105 d.SetId(d.Get("name").(string)) 106 107 log.Printf("[INFO] DB Security Group ID: %s", d.Id()) 108 109 sg, err := resourceAwsDbSecurityGroupRetrieve(d, meta) 110 if err != nil { 111 return err 112 } 113 114 ingresses := d.Get("ingress").(*schema.Set) 115 for _, ing := range ingresses.List() { 116 err := resourceAwsDbSecurityGroupAuthorizeRule(ing, *sg.DBSecurityGroupName, conn) 117 if err != nil { 118 errs = append(errs, err) 119 } 120 } 121 122 if len(errs) > 0 { 123 return &multierror.Error{Errors: errs} 124 } 125 126 log.Println( 127 "[INFO] Waiting for Ingress Authorizations to be authorized") 128 129 stateConf := &resource.StateChangeConf{ 130 Pending: []string{"authorizing"}, 131 Target: []string{"authorized"}, 132 Refresh: resourceAwsDbSecurityGroupStateRefreshFunc(d, meta), 133 Timeout: 10 * time.Minute, 134 } 135 136 // Wait, catching any errors 137 _, err = stateConf.WaitForState() 138 if err != nil { 139 return err 140 } 141 142 return resourceAwsDbSecurityGroupRead(d, meta) 143 } 144 145 func resourceAwsDbSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { 146 sg, err := resourceAwsDbSecurityGroupRetrieve(d, meta) 147 if err != nil { 148 return err 149 } 150 151 d.Set("name", *sg.DBSecurityGroupName) 152 d.Set("description", *sg.DBSecurityGroupDescription) 153 154 // Create an empty schema.Set to hold all ingress rules 155 rules := &schema.Set{ 156 F: resourceAwsDbSecurityGroupIngressHash, 157 } 158 159 for _, v := range sg.IPRanges { 160 rule := map[string]interface{}{"cidr": *v.CIDRIP} 161 rules.Add(rule) 162 } 163 164 for _, g := range sg.EC2SecurityGroups { 165 rule := map[string]interface{}{ 166 "security_group_name": *g.EC2SecurityGroupName, 167 "security_group_id": *g.EC2SecurityGroupId, 168 "security_group_owner_id": *g.EC2SecurityGroupOwnerId, 169 } 170 rules.Add(rule) 171 } 172 173 d.Set("ingress", rules) 174 175 conn := meta.(*AWSClient).rdsconn 176 arn, err := buildRDSSecurityGroupARN(d, meta) 177 if err != nil { 178 name := "<empty>" 179 if sg.DBSecurityGroupName != nil && *sg.DBSecurityGroupName != "" { 180 name = *sg.DBSecurityGroupName 181 } 182 log.Printf("[DEBUG] Error building ARN for DB Security Group, not setting Tags for DB Security Group %s", name) 183 } else { 184 d.Set("arn", arn) 185 resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceInput{ 186 ResourceName: aws.String(arn), 187 }) 188 189 if err != nil { 190 log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn) 191 } 192 193 var dt []*rds.Tag 194 if len(resp.TagList) > 0 { 195 dt = resp.TagList 196 } 197 d.Set("tags", tagsToMapRDS(dt)) 198 } 199 200 return nil 201 } 202 203 func resourceAwsDbSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { 204 conn := meta.(*AWSClient).rdsconn 205 206 d.Partial(true) 207 if arn, err := buildRDSSecurityGroupARN(d, meta); err == nil { 208 if err := setTagsRDS(conn, d, arn); err != nil { 209 return err 210 } else { 211 d.SetPartial("tags") 212 } 213 } 214 215 if d.HasChange("ingress") { 216 sg, err := resourceAwsDbSecurityGroupRetrieve(d, meta) 217 if err != nil { 218 return err 219 } 220 221 oi, ni := d.GetChange("ingress") 222 if oi == nil { 223 oi = new(schema.Set) 224 } 225 if ni == nil { 226 ni = new(schema.Set) 227 } 228 229 ois := oi.(*schema.Set) 230 nis := ni.(*schema.Set) 231 removeIngress := ois.Difference(nis).List() 232 newIngress := nis.Difference(ois).List() 233 234 // DELETE old Ingress rules 235 for _, ing := range removeIngress { 236 err := resourceAwsDbSecurityGroupRevokeRule(ing, *sg.DBSecurityGroupName, conn) 237 if err != nil { 238 return err 239 } 240 } 241 242 // ADD new/updated Ingress rules 243 for _, ing := range newIngress { 244 err := resourceAwsDbSecurityGroupAuthorizeRule(ing, *sg.DBSecurityGroupName, conn) 245 if err != nil { 246 return err 247 } 248 } 249 } 250 d.Partial(false) 251 252 return resourceAwsDbSecurityGroupRead(d, meta) 253 } 254 255 func resourceAwsDbSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { 256 conn := meta.(*AWSClient).rdsconn 257 258 log.Printf("[DEBUG] DB Security Group destroy: %v", d.Id()) 259 260 opts := rds.DeleteDBSecurityGroupInput{DBSecurityGroupName: aws.String(d.Id())} 261 262 log.Printf("[DEBUG] DB Security Group destroy configuration: %v", opts) 263 _, err := conn.DeleteDBSecurityGroup(&opts) 264 265 if err != nil { 266 newerr, ok := err.(awserr.Error) 267 if ok && newerr.Code() == "InvalidDBSecurityGroup.NotFound" { 268 return nil 269 } 270 return err 271 } 272 273 return nil 274 } 275 276 func resourceAwsDbSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*rds.DBSecurityGroup, error) { 277 conn := meta.(*AWSClient).rdsconn 278 279 opts := rds.DescribeDBSecurityGroupsInput{ 280 DBSecurityGroupName: aws.String(d.Id()), 281 } 282 283 log.Printf("[DEBUG] DB Security Group describe configuration: %#v", opts) 284 285 resp, err := conn.DescribeDBSecurityGroups(&opts) 286 287 if err != nil { 288 return nil, fmt.Errorf("Error retrieving DB Security Groups: %s", err) 289 } 290 291 if len(resp.DBSecurityGroups) != 1 || 292 *resp.DBSecurityGroups[0].DBSecurityGroupName != d.Id() { 293 return nil, fmt.Errorf("Unable to find DB Security Group: %#v", resp.DBSecurityGroups) 294 } 295 296 return resp.DBSecurityGroups[0], nil 297 } 298 299 // Authorizes the ingress rule on the db security group 300 func resourceAwsDbSecurityGroupAuthorizeRule(ingress interface{}, dbSecurityGroupName string, conn *rds.RDS) error { 301 ing := ingress.(map[string]interface{}) 302 303 opts := rds.AuthorizeDBSecurityGroupIngressInput{ 304 DBSecurityGroupName: aws.String(dbSecurityGroupName), 305 } 306 307 if attr, ok := ing["cidr"]; ok && attr != "" { 308 opts.CIDRIP = aws.String(attr.(string)) 309 } 310 311 if attr, ok := ing["security_group_name"]; ok && attr != "" { 312 opts.EC2SecurityGroupName = aws.String(attr.(string)) 313 } 314 315 if attr, ok := ing["security_group_id"]; ok && attr != "" { 316 opts.EC2SecurityGroupId = aws.String(attr.(string)) 317 } 318 319 if attr, ok := ing["security_group_owner_id"]; ok && attr != "" { 320 opts.EC2SecurityGroupOwnerId = aws.String(attr.(string)) 321 } 322 323 log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts) 324 325 _, err := conn.AuthorizeDBSecurityGroupIngress(&opts) 326 327 if err != nil { 328 return fmt.Errorf("Error authorizing security group ingress: %s", err) 329 } 330 331 return nil 332 } 333 334 // Revokes the ingress rule on the db security group 335 func resourceAwsDbSecurityGroupRevokeRule(ingress interface{}, dbSecurityGroupName string, conn *rds.RDS) error { 336 ing := ingress.(map[string]interface{}) 337 338 opts := rds.RevokeDBSecurityGroupIngressInput{ 339 DBSecurityGroupName: aws.String(dbSecurityGroupName), 340 } 341 342 if attr, ok := ing["cidr"]; ok && attr != "" { 343 opts.CIDRIP = aws.String(attr.(string)) 344 } 345 346 if attr, ok := ing["security_group_name"]; ok && attr != "" { 347 opts.EC2SecurityGroupName = aws.String(attr.(string)) 348 } 349 350 if attr, ok := ing["security_group_id"]; ok && attr != "" { 351 opts.EC2SecurityGroupId = aws.String(attr.(string)) 352 } 353 354 if attr, ok := ing["security_group_owner_id"]; ok && attr != "" { 355 opts.EC2SecurityGroupOwnerId = aws.String(attr.(string)) 356 } 357 358 log.Printf("[DEBUG] Revoking ingress rule configuration: %#v", opts) 359 360 _, err := conn.RevokeDBSecurityGroupIngress(&opts) 361 362 if err != nil { 363 return fmt.Errorf("Error revoking security group ingress: %s", err) 364 } 365 366 return nil 367 } 368 369 func resourceAwsDbSecurityGroupIngressHash(v interface{}) int { 370 var buf bytes.Buffer 371 m := v.(map[string]interface{}) 372 373 if v, ok := m["cidr"]; ok { 374 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 375 } 376 377 if v, ok := m["security_group_name"]; ok { 378 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 379 } 380 381 if v, ok := m["security_group_id"]; ok { 382 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 383 } 384 385 if v, ok := m["security_group_owner_id"]; ok { 386 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 387 } 388 389 return hashcode.String(buf.String()) 390 } 391 392 func resourceAwsDbSecurityGroupStateRefreshFunc( 393 d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 394 return func() (interface{}, string, error) { 395 v, err := resourceAwsDbSecurityGroupRetrieve(d, meta) 396 397 if err != nil { 398 log.Printf("Error on retrieving DB Security Group when waiting: %s", err) 399 return nil, "", err 400 } 401 402 statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges)) 403 for _, ec2g := range v.EC2SecurityGroups { 404 statuses = append(statuses, *ec2g.Status) 405 } 406 for _, ips := range v.IPRanges { 407 statuses = append(statuses, *ips.Status) 408 } 409 410 for _, stat := range statuses { 411 // Not done 412 if stat != "authorized" { 413 return nil, "authorizing", nil 414 } 415 } 416 417 return v, "authorized", nil 418 } 419 } 420 421 func buildRDSSecurityGroupARN(d *schema.ResourceData, meta interface{}) (string, error) { 422 iamconn := meta.(*AWSClient).iamconn 423 region := meta.(*AWSClient).region 424 // An zero value GetUserInput{} defers to the currently logged in user 425 resp, err := iamconn.GetUser(&iam.GetUserInput{}) 426 if err != nil { 427 return "", err 428 } 429 userARN := *resp.User.Arn 430 accountID := strings.Split(userARN, ":")[4] 431 arn := fmt.Sprintf("arn:aws:rds:%s:%s:secgrp:%s", region, accountID, d.Id()) 432 return arn, nil 433 }