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