github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/builtin/providers/aws/resource_aws_redshift_security_group.go (about) 1 package aws 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "regexp" 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/redshift" 13 "github.com/hashicorp/go-multierror" 14 "github.com/hashicorp/terraform/helper/hashcode" 15 "github.com/hashicorp/terraform/helper/resource" 16 "github.com/hashicorp/terraform/helper/schema" 17 ) 18 19 func resourceAwsRedshiftSecurityGroup() *schema.Resource { 20 return &schema.Resource{ 21 Create: resourceAwsRedshiftSecurityGroupCreate, 22 Read: resourceAwsRedshiftSecurityGroupRead, 23 Update: resourceAwsRedshiftSecurityGroupUpdate, 24 Delete: resourceAwsRedshiftSecurityGroupDelete, 25 26 Schema: map[string]*schema.Schema{ 27 "name": &schema.Schema{ 28 Type: schema.TypeString, 29 Required: true, 30 ForceNew: true, 31 ValidateFunc: validateRedshiftSecurityGroupName, 32 }, 33 34 "description": &schema.Schema{ 35 Type: schema.TypeString, 36 Optional: true, 37 ForceNew: true, 38 Default: "Managed by Terraform", 39 }, 40 41 "ingress": &schema.Schema{ 42 Type: schema.TypeSet, 43 Required: true, 44 Elem: &schema.Resource{ 45 Schema: map[string]*schema.Schema{ 46 "cidr": &schema.Schema{ 47 Type: schema.TypeString, 48 Optional: true, 49 }, 50 51 "security_group_name": &schema.Schema{ 52 Type: schema.TypeString, 53 Optional: true, 54 Computed: true, 55 }, 56 57 "security_group_owner_id": &schema.Schema{ 58 Type: schema.TypeString, 59 Optional: true, 60 Computed: true, 61 }, 62 }, 63 }, 64 Set: resourceAwsRedshiftSecurityGroupIngressHash, 65 }, 66 }, 67 } 68 } 69 70 func resourceAwsRedshiftSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { 71 conn := meta.(*AWSClient).redshiftconn 72 73 var err error 74 var errs []error 75 76 name := d.Get("name").(string) 77 desc := d.Get("description").(string) 78 sgInput := &redshift.CreateClusterSecurityGroupInput{ 79 ClusterSecurityGroupName: aws.String(name), 80 Description: aws.String(desc), 81 } 82 log.Printf("[DEBUG] Redshift security group create: name: %s, description: %s", name, desc) 83 _, err = conn.CreateClusterSecurityGroup(sgInput) 84 if err != nil { 85 return fmt.Errorf("Error creating RedshiftSecurityGroup: %s", err) 86 } 87 88 d.SetId(d.Get("name").(string)) 89 90 log.Printf("[INFO] Redshift Security Group ID: %s", d.Id()) 91 sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta) 92 if err != nil { 93 return err 94 } 95 96 ingresses := d.Get("ingress").(*schema.Set) 97 for _, ing := range ingresses.List() { 98 err := resourceAwsRedshiftSecurityGroupAuthorizeRule(ing, *sg.ClusterSecurityGroupName, conn) 99 if err != nil { 100 errs = append(errs, err) 101 } 102 } 103 104 if len(errs) > 0 { 105 return &multierror.Error{Errors: errs} 106 } 107 108 log.Println("[INFO] Waiting for Redshift Security Group Ingress Authorizations to be authorized") 109 stateConf := &resource.StateChangeConf{ 110 Pending: []string{"authorizing"}, 111 Target: []string{"authorized"}, 112 Refresh: resourceAwsRedshiftSecurityGroupStateRefreshFunc(d, meta), 113 Timeout: 10 * time.Minute, 114 } 115 116 _, err = stateConf.WaitForState() 117 if err != nil { 118 return err 119 } 120 121 return resourceAwsRedshiftSecurityGroupRead(d, meta) 122 } 123 124 func resourceAwsRedshiftSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { 125 sg, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta) 126 if err != nil { 127 return err 128 } 129 130 rules := &schema.Set{ 131 F: resourceAwsRedshiftSecurityGroupIngressHash, 132 } 133 134 for _, v := range sg.IPRanges { 135 rule := map[string]interface{}{"cidr": *v.CIDRIP} 136 rules.Add(rule) 137 } 138 139 for _, g := range sg.EC2SecurityGroups { 140 rule := map[string]interface{}{ 141 "security_group_name": *g.EC2SecurityGroupName, 142 "security_group_owner_id": *g.EC2SecurityGroupOwnerId, 143 } 144 rules.Add(rule) 145 } 146 147 d.Set("ingress", rules) 148 d.Set("name", *sg.ClusterSecurityGroupName) 149 d.Set("description", *sg.Description) 150 151 return nil 152 } 153 154 func resourceAwsRedshiftSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { 155 conn := meta.(*AWSClient).redshiftconn 156 157 if d.HasChange("ingress") { 158 o, n := d.GetChange("ingress") 159 if o == nil { 160 o = new(schema.Set) 161 } 162 if n == nil { 163 n = new(schema.Set) 164 } 165 166 os := o.(*schema.Set) 167 ns := n.(*schema.Set) 168 169 removeIngressRules, err := expandRedshiftSGRevokeIngress(os.Difference(ns).List()) 170 if err != nil { 171 return err 172 } 173 if len(removeIngressRules) > 0 { 174 for _, r := range removeIngressRules { 175 r.ClusterSecurityGroupName = aws.String(d.Id()) 176 177 _, err := conn.RevokeClusterSecurityGroupIngress(&r) 178 if err != nil { 179 return err 180 } 181 } 182 } 183 184 addIngressRules, err := expandRedshiftSGAuthorizeIngress(ns.Difference(os).List()) 185 if err != nil { 186 return err 187 } 188 if len(addIngressRules) > 0 { 189 for _, r := range addIngressRules { 190 r.ClusterSecurityGroupName = aws.String(d.Id()) 191 192 _, err := conn.AuthorizeClusterSecurityGroupIngress(&r) 193 if err != nil { 194 return err 195 } 196 } 197 } 198 199 } 200 return resourceAwsRedshiftSecurityGroupRead(d, meta) 201 } 202 203 func resourceAwsRedshiftSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { 204 conn := meta.(*AWSClient).redshiftconn 205 206 log.Printf("[DEBUG] Redshift Security Group destroy: %v", d.Id()) 207 opts := redshift.DeleteClusterSecurityGroupInput{ 208 ClusterSecurityGroupName: aws.String(d.Id()), 209 } 210 211 log.Printf("[DEBUG] Redshift Security Group destroy configuration: %v", opts) 212 _, err := conn.DeleteClusterSecurityGroup(&opts) 213 214 if err != nil { 215 newerr, ok := err.(awserr.Error) 216 if ok && newerr.Code() == "InvalidRedshiftSecurityGroup.NotFound" { 217 return nil 218 } 219 return err 220 } 221 222 return nil 223 } 224 225 func resourceAwsRedshiftSecurityGroupRetrieve(d *schema.ResourceData, meta interface{}) (*redshift.ClusterSecurityGroup, error) { 226 conn := meta.(*AWSClient).redshiftconn 227 228 opts := redshift.DescribeClusterSecurityGroupsInput{ 229 ClusterSecurityGroupName: aws.String(d.Id()), 230 } 231 232 log.Printf("[DEBUG] Redshift Security Group describe configuration: %#v", opts) 233 234 resp, err := conn.DescribeClusterSecurityGroups(&opts) 235 236 if err != nil { 237 return nil, fmt.Errorf("Error retrieving Redshift Security Groups: %s", err) 238 } 239 240 if len(resp.ClusterSecurityGroups) != 1 || 241 *resp.ClusterSecurityGroups[0].ClusterSecurityGroupName != d.Id() { 242 return nil, fmt.Errorf("Unable to find Redshift Security Group: %#v", resp.ClusterSecurityGroups) 243 } 244 245 return resp.ClusterSecurityGroups[0], nil 246 } 247 248 func validateRedshiftSecurityGroupName(v interface{}, k string) (ws []string, errors []error) { 249 value := v.(string) 250 if value == "default" { 251 errors = append(errors, fmt.Errorf("the Redshift Security Group name cannot be %q", value)) 252 } 253 if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) { 254 errors = append(errors, fmt.Errorf( 255 "only lowercase alphanumeric characters and hyphens allowed in %q: %q", 256 k, value)) 257 } 258 if len(value) > 255 { 259 errors = append(errors, fmt.Errorf( 260 "%q cannot be longer than 32 characters: %q", k, value)) 261 } 262 return 263 264 } 265 266 func resourceAwsRedshiftSecurityGroupIngressHash(v interface{}) int { 267 var buf bytes.Buffer 268 m := v.(map[string]interface{}) 269 270 if v, ok := m["cidr"]; ok { 271 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 272 } 273 274 if v, ok := m["security_group_name"]; ok { 275 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 276 } 277 278 if v, ok := m["security_group_owner_id"]; ok { 279 buf.WriteString(fmt.Sprintf("%s-", v.(string))) 280 } 281 282 return hashcode.String(buf.String()) 283 } 284 285 func resourceAwsRedshiftSecurityGroupAuthorizeRule(ingress interface{}, redshiftSecurityGroupName string, conn *redshift.Redshift) error { 286 ing := ingress.(map[string]interface{}) 287 288 opts := redshift.AuthorizeClusterSecurityGroupIngressInput{ 289 ClusterSecurityGroupName: aws.String(redshiftSecurityGroupName), 290 } 291 292 if attr, ok := ing["cidr"]; ok && attr != "" { 293 opts.CIDRIP = aws.String(attr.(string)) 294 } 295 296 if attr, ok := ing["security_group_name"]; ok && attr != "" { 297 opts.EC2SecurityGroupName = aws.String(attr.(string)) 298 } 299 300 if attr, ok := ing["security_group_owner_id"]; ok && attr != "" { 301 opts.EC2SecurityGroupOwnerId = aws.String(attr.(string)) 302 } 303 304 log.Printf("[DEBUG] Authorize ingress rule configuration: %#v", opts) 305 _, err := conn.AuthorizeClusterSecurityGroupIngress(&opts) 306 307 if err != nil { 308 return fmt.Errorf("Error authorizing security group ingress: %s", err) 309 } 310 311 return nil 312 } 313 314 func resourceAwsRedshiftSecurityGroupStateRefreshFunc( 315 d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { 316 return func() (interface{}, string, error) { 317 v, err := resourceAwsRedshiftSecurityGroupRetrieve(d, meta) 318 319 if err != nil { 320 log.Printf("Error on retrieving Redshift Security Group when waiting: %s", err) 321 return nil, "", err 322 } 323 324 statuses := make([]string, 0, len(v.EC2SecurityGroups)+len(v.IPRanges)) 325 for _, ec2g := range v.EC2SecurityGroups { 326 statuses = append(statuses, *ec2g.Status) 327 } 328 for _, ips := range v.IPRanges { 329 statuses = append(statuses, *ips.Status) 330 } 331 332 for _, stat := range statuses { 333 // Not done 334 if stat != "authorized" { 335 return nil, "authorizing", nil 336 } 337 } 338 339 return v, "authorized", nil 340 } 341 } 342 343 func expandRedshiftSGAuthorizeIngress(configured []interface{}) ([]redshift.AuthorizeClusterSecurityGroupIngressInput, error) { 344 var ingress []redshift.AuthorizeClusterSecurityGroupIngressInput 345 346 // Loop over our configured parameters and create 347 // an array of aws-sdk-go compatabile objects 348 for _, pRaw := range configured { 349 data := pRaw.(map[string]interface{}) 350 351 i := redshift.AuthorizeClusterSecurityGroupIngressInput{} 352 353 if v, ok := data["cidr"]; ok { 354 i.CIDRIP = aws.String(v.(string)) 355 } 356 357 if v, ok := data["security_group_name"]; ok { 358 i.EC2SecurityGroupName = aws.String(v.(string)) 359 } 360 361 if v, ok := data["security_group_owner_id"]; ok { 362 i.EC2SecurityGroupOwnerId = aws.String(v.(string)) 363 } 364 365 ingress = append(ingress, i) 366 } 367 368 return ingress, nil 369 } 370 371 func expandRedshiftSGRevokeIngress(configured []interface{}) ([]redshift.RevokeClusterSecurityGroupIngressInput, error) { 372 var ingress []redshift.RevokeClusterSecurityGroupIngressInput 373 374 // Loop over our configured parameters and create 375 // an array of aws-sdk-go compatabile objects 376 for _, pRaw := range configured { 377 data := pRaw.(map[string]interface{}) 378 379 i := redshift.RevokeClusterSecurityGroupIngressInput{} 380 381 if v, ok := data["cidr"]; ok { 382 i.CIDRIP = aws.String(v.(string)) 383 } 384 385 if v, ok := data["security_group_name"]; ok { 386 i.EC2SecurityGroupName = aws.String(v.(string)) 387 } 388 389 if v, ok := data["security_group_owner_id"]; ok { 390 i.EC2SecurityGroupOwnerId = aws.String(v.(string)) 391 } 392 393 ingress = append(ingress, i) 394 } 395 396 return ingress, nil 397 }