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