github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 if g.EC2SecurityGroupId != nil { 165 rule["security_group_id"] = *g.EC2SecurityGroupId 166 } 167 if g.EC2SecurityGroupName != nil { 168 rule["security_group_name"] = *g.EC2SecurityGroupName 169 } 170 if g.EC2SecurityGroupOwnerId != nil { 171 rule["security_group_owner_id"] = *g.EC2SecurityGroupOwnerId 172 } 173 rules.Add(rule) 174 } 175 176 d.Set("ingress", rules) 177 178 conn := meta.(*AWSClient).rdsconn 179 arn, err := buildRDSSecurityGroupARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region) 180 if err != nil { 181 name := "<empty>" 182 if sg.DBSecurityGroupName != nil && *sg.DBSecurityGroupName != "" { 183 name = *sg.DBSecurityGroupName 184 } 185 log.Printf("[DEBUG] Error building ARN for DB Security Group, not setting Tags for DB Security Group %s", name) 186 } else { 187 d.Set("arn", arn) 188 resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceInput{ 189 ResourceName: aws.String(arn), 190 }) 191 192 if err != nil { 193 log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn) 194 } 195 196 var dt []*rds.Tag 197 if len(resp.TagList) > 0 { 198 dt = resp.TagList 199 } 200 d.Set("tags", tagsToMapRDS(dt)) 201 } 202 203 return nil 204 } 205 206 func resourceAwsDbSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { 207 conn := meta.(*AWSClient).rdsconn 208 209 d.Partial(true) 210 if arn, err := buildRDSSecurityGroupARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region); err == nil { 211 if err := setTagsRDS(conn, d, arn); err != nil { 212 return err 213 } else { 214 d.SetPartial("tags") 215 } 216 } 217 218 if d.HasChange("ingress") { 219 sg, err := resourceAwsDbSecurityGroupRetrieve(d, meta) 220 if err != nil { 221 return err 222 } 223 224 oi, ni := d.GetChange("ingress") 225 if oi == nil { 226 oi = new(schema.Set) 227 } 228 if ni == nil { 229 ni = new(schema.Set) 230 } 231 232 ois := oi.(*schema.Set) 233 nis := ni.(*schema.Set) 234 removeIngress := ois.Difference(nis).List() 235 newIngress := nis.Difference(ois).List() 236 237 // DELETE old Ingress rules 238 for _, ing := range removeIngress { 239 err := resourceAwsDbSecurityGroupRevokeRule(ing, *sg.DBSecurityGroupName, conn) 240 if err != nil { 241 return err 242 } 243 } 244 245 // ADD new/updated Ingress rules 246 for _, ing := range newIngress { 247 err := resourceAwsDbSecurityGroupAuthorizeRule(ing, *sg.DBSecurityGroupName, conn) 248 if err != nil { 249 return err 250 } 251 } 252 } 253 d.Partial(false) 254 255 return resourceAwsDbSecurityGroupRead(d, meta) 256 } 257 258 func resourceAwsDbSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { 259 conn := meta.(*AWSClient).rdsconn 260 261 log.Printf("[DEBUG] DB Security Group destroy: %v", d.Id()) 262 263 opts := rds.DeleteDBSecurityGroupInput{DBSecurityGroupName: aws.String(d.Id())} 264 265 log.Printf("[DEBUG] DB Security Group destroy configuration: %v", opts) 266 _, err := conn.DeleteDBSecurityGroup(&opts) 267 268 if err != nil { 269 newerr, ok := err.(awserr.Error) 270 if ok && newerr.Code() == "InvalidDBSecurityGroup.NotFound" { 271 return nil 272 } 273 return err 274 } 275 276 return nil 277 } 278 279 func resourceAwsDbSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*rds.DBSecurityGroup, error) { 280 conn := meta.(*AWSClient).rdsconn 281 282 opts := rds.DescribeDBSecurityGroupsInput{ 283 DBSecurityGroupName: aws.String(d.Id()), 284 } 285 286 log.Printf("[DEBUG] DB Security Group describe configuration: %#v", opts) 287 288 resp, err := conn.DescribeDBSecurityGroups(&opts) 289 290 if err != nil { 291 return nil, fmt.Errorf("Error retrieving DB Security Groups: %s", err) 292 } 293 294 if len(resp.DBSecurityGroups) != 1 || 295 *resp.DBSecurityGroups[0].DBSecurityGroupName != d.Id() { 296 return nil, fmt.Errorf("Unable to find DB Security Group: %#v", resp.DBSecurityGroups) 297 } 298 299 return resp.DBSecurityGroups[0], nil 300 } 301 302 // Authorizes the ingress rule on the db security group 303 func resourceAwsDbSecurityGroupAuthorizeRule(ingress interface{}, dbSecurityGroupName string, conn *rds.RDS) error { 304 ing := ingress.(map[string]interface{}) 305 306 opts := rds.AuthorizeDBSecurityGroupIngressInput{ 307 DBSecurityGroupName: aws.String(dbSecurityGroupName), 308 } 309 310 if attr, ok := ing["cidr"]; ok && attr != "" { 311 opts.CIDRIP = aws.String(attr.(string)) 312 } 313 314 if attr, ok := ing["security_group_name"]; ok && attr != "" { 315 opts.EC2SecurityGroupName = aws.String(attr.(string)) 316 } 317 318 if attr, ok := ing["security_group_id"]; ok && attr != "" { 319 opts.EC2SecurityGroupId = aws.String(attr.(string)) 320 } 321 322 if attr, ok := ing["security_group_owner_id"]; ok && attr != "" { 323 opts.EC2SecurityGroupOwnerId = aws.String(attr.(string)) 324 } 325 326 log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts) 327 328 _, err := conn.AuthorizeDBSecurityGroupIngress(&opts) 329 330 if err != nil { 331 return fmt.Errorf("Error authorizing security group ingress: %s", err) 332 } 333 334 return nil 335 } 336 337 // Revokes the ingress rule on the db security group 338 func resourceAwsDbSecurityGroupRevokeRule(ingress interface{}, dbSecurityGroupName string, conn *rds.RDS) error { 339 ing := ingress.(map[string]interface{}) 340 341 opts := rds.RevokeDBSecurityGroupIngressInput{ 342 DBSecurityGroupName: aws.String(dbSecurityGroupName), 343 } 344 345 if attr, ok := ing["cidr"]; ok && attr != "" { 346 opts.CIDRIP = aws.String(attr.(string)) 347 } 348 349 if attr, ok := ing["security_group_name"]; ok && attr != "" { 350 opts.EC2SecurityGroupName = aws.String(attr.(string)) 351 } 352 353 if attr, ok := ing["security_group_id"]; ok && attr != "" { 354 opts.EC2SecurityGroupId = aws.String(attr.(string)) 355 } 356 357 if attr, ok := ing["security_group_owner_id"]; ok && attr != "" { 358 opts.EC2SecurityGroupOwnerId = aws.String(attr.(string)) 359 } 360 361 log.Printf("[DEBUG] Revoking ingress rule configuration: %#v", opts) 362 363 _, err := conn.RevokeDBSecurityGroupIngress(&opts) 364 365 if err != nil { 366 return fmt.Errorf("Error revoking security group ingress: %s", err) 367 } 368 369 return nil 370 } 371 372 func resourceAwsDbSecurityGroupIngressHash(v interface{}) int { 373 var buf bytes.Buffer 374 m := v.(map[string]interface{}) 375 376 if v, ok := m["cidr"]; ok { 377 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 378 } 379 380 if v, ok := m["security_group_name"]; ok { 381 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 382 } 383 384 if v, ok := m["security_group_id"]; ok { 385 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 386 } 387 388 if v, ok := m["security_group_owner_id"]; ok { 389 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 390 } 391 392 return hashcode.String(buf.String()) 393 } 394 395 func resourceAwsDbSecurityGroupStateRefreshFunc( 396 d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 397 return func() (interface{}, string, error) { 398 v, err := resourceAwsDbSecurityGroupRetrieve(d, meta) 399 400 if err != nil { 401 log.Printf("Error on retrieving DB Security Group when waiting: %s", err) 402 return nil, "", err 403 } 404 405 statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges)) 406 for _, ec2g := range v.EC2SecurityGroups { 407 statuses = append(statuses, *ec2g.Status) 408 } 409 for _, ips := range v.IPRanges { 410 statuses = append(statuses, *ips.Status) 411 } 412 413 for _, stat := range statuses { 414 // Not done 415 if stat != "authorized" { 416 return nil, "authorizing", nil 417 } 418 } 419 420 return v, "authorized", nil 421 } 422 } 423 424 func buildRDSSecurityGroupARN(identifier, partition, accountid, region string) (string, error) { 425 if partition == "" { 426 return "", fmt.Errorf("Unable to construct RDS ARN because of missing AWS partition") 427 } 428 if accountid == "" { 429 return "", fmt.Errorf("Unable to construct RDS ARN because of missing AWS Account ID") 430 } 431 arn := fmt.Sprintf("arn:%s:rds:%s:%s:secgrp:%s", partition, region, accountid, identifier) 432 return arn, nil 433 434 }