github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_db_option_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/rds" 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 resourceAwsDbOptionGroup() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceAwsDbOptionGroupCreate, 21 Read: resourceAwsDbOptionGroupRead, 22 Update: resourceAwsDbOptionGroupUpdate, 23 Delete: resourceAwsDbOptionGroupDelete, 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 "name": &schema.Schema{ 34 Type: schema.TypeString, 35 ForceNew: true, 36 Required: true, 37 ValidateFunc: validateDbOptionGroupName, 38 }, 39 "engine_name": &schema.Schema{ 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: true, 43 }, 44 "major_engine_version": &schema.Schema{ 45 Type: schema.TypeString, 46 Required: true, 47 ForceNew: true, 48 }, 49 "option_group_description": &schema.Schema{ 50 Type: schema.TypeString, 51 Required: true, 52 ForceNew: true, 53 }, 54 55 "option": &schema.Schema{ 56 Type: schema.TypeSet, 57 Optional: true, 58 Elem: &schema.Resource{ 59 Schema: map[string]*schema.Schema{ 60 "option_name": &schema.Schema{ 61 Type: schema.TypeString, 62 Required: true, 63 }, 64 "option_settings": &schema.Schema{ 65 Type: schema.TypeSet, 66 Optional: true, 67 Elem: &schema.Resource{ 68 Schema: map[string]*schema.Schema{ 69 "name": &schema.Schema{ 70 Type: schema.TypeString, 71 Required: true, 72 }, 73 "value": &schema.Schema{ 74 Type: schema.TypeString, 75 Required: true, 76 }, 77 }, 78 }, 79 }, 80 "port": &schema.Schema{ 81 Type: schema.TypeInt, 82 Optional: true, 83 }, 84 "db_security_group_memberships": &schema.Schema{ 85 Type: schema.TypeSet, 86 Optional: true, 87 Elem: &schema.Schema{Type: schema.TypeString}, 88 Set: schema.HashString, 89 }, 90 "vpc_security_group_memberships": &schema.Schema{ 91 Type: schema.TypeSet, 92 Optional: true, 93 Elem: &schema.Schema{Type: schema.TypeString}, 94 Set: schema.HashString, 95 }, 96 }, 97 }, 98 Set: resourceAwsDbOptionHash, 99 }, 100 101 "tags": tagsSchema(), 102 }, 103 } 104 } 105 106 func resourceAwsDbOptionGroupCreate(d *schema.ResourceData, meta interface{}) error { 107 rdsconn := meta.(*AWSClient).rdsconn 108 tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{})) 109 110 createOpts := &rds.CreateOptionGroupInput{ 111 EngineName: aws.String(d.Get("engine_name").(string)), 112 MajorEngineVersion: aws.String(d.Get("major_engine_version").(string)), 113 OptionGroupDescription: aws.String(d.Get("option_group_description").(string)), 114 OptionGroupName: aws.String(d.Get("name").(string)), 115 Tags: tags, 116 } 117 118 log.Printf("[DEBUG] Create DB Option Group: %#v", createOpts) 119 _, err := rdsconn.CreateOptionGroup(createOpts) 120 if err != nil { 121 return fmt.Errorf("Error creating DB Option Group: %s", err) 122 } 123 124 d.SetId(d.Get("name").(string)) 125 log.Printf("[INFO] DB Option Group ID: %s", d.Id()) 126 127 return resourceAwsDbOptionGroupUpdate(d, meta) 128 } 129 130 func resourceAwsDbOptionGroupRead(d *schema.ResourceData, meta interface{}) error { 131 rdsconn := meta.(*AWSClient).rdsconn 132 params := &rds.DescribeOptionGroupsInput{ 133 OptionGroupName: aws.String(d.Id()), 134 } 135 136 log.Printf("[DEBUG] Describe DB Option Group: %#v", params) 137 options, err := rdsconn.DescribeOptionGroups(params) 138 if err != nil { 139 if awsErr, ok := err.(awserr.Error); ok { 140 if "OptionGroupNotFoundFault" == awsErr.Code() { 141 d.SetId("") 142 log.Printf("[DEBUG] DB Option Group (%s) not found", d.Get("name").(string)) 143 return nil 144 } 145 } 146 return fmt.Errorf("Error Describing DB Option Group: %s", err) 147 } 148 149 var option *rds.OptionGroup 150 for _, ogl := range options.OptionGroupsList { 151 if *ogl.OptionGroupName == d.Id() { 152 option = ogl 153 break 154 } 155 } 156 157 if option == nil { 158 return fmt.Errorf("Unable to find Option Group: %#v", options.OptionGroupsList) 159 } 160 161 d.Set("name", option.OptionGroupName) 162 d.Set("major_engine_version", option.MajorEngineVersion) 163 d.Set("engine_name", option.EngineName) 164 d.Set("option_group_description", option.OptionGroupDescription) 165 if len(option.Options) != 0 { 166 d.Set("option", flattenOptions(option.Options)) 167 } 168 169 optionGroup := options.OptionGroupsList[0] 170 arn, err := buildRDSOptionGroupARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region) 171 if err != nil { 172 name := "<empty>" 173 if optionGroup.OptionGroupName != nil && *optionGroup.OptionGroupName != "" { 174 name = *optionGroup.OptionGroupName 175 } 176 log.Printf("[DEBUG] Error building ARN for DB Option Group, not setting Tags for Option Group %s", name) 177 } else { 178 d.Set("arn", arn) 179 resp, err := rdsconn.ListTagsForResource(&rds.ListTagsForResourceInput{ 180 ResourceName: aws.String(arn), 181 }) 182 183 if err != nil { 184 log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn) 185 } 186 187 var dt []*rds.Tag 188 if len(resp.TagList) > 0 { 189 dt = resp.TagList 190 } 191 d.Set("tags", tagsToMapRDS(dt)) 192 } 193 194 return nil 195 } 196 197 func optionInList(optionName string, list []*string) bool { 198 for _, opt := range list { 199 if *opt == optionName { 200 return true 201 } 202 } 203 return false 204 } 205 206 func resourceAwsDbOptionGroupUpdate(d *schema.ResourceData, meta interface{}) error { 207 rdsconn := meta.(*AWSClient).rdsconn 208 if d.HasChange("option") { 209 o, n := d.GetChange("option") 210 if o == nil { 211 o = new(schema.Set) 212 } 213 if n == nil { 214 n = new(schema.Set) 215 } 216 217 os := o.(*schema.Set) 218 ns := n.(*schema.Set) 219 addOptions, addErr := expandOptionConfiguration(ns.Difference(os).List()) 220 if addErr != nil { 221 return addErr 222 } 223 224 addingOptionNames, err := flattenOptionNames(ns.Difference(os).List()) 225 if err != nil { 226 return err 227 } 228 229 removeOptions := []*string{} 230 opts, err := flattenOptionNames(os.Difference(ns).List()) 231 if err != nil { 232 return err 233 } 234 235 for _, optionName := range opts { 236 if optionInList(*optionName, addingOptionNames) { 237 continue 238 } 239 removeOptions = append(removeOptions, optionName) 240 } 241 242 modifyOpts := &rds.ModifyOptionGroupInput{ 243 OptionGroupName: aws.String(d.Id()), 244 ApplyImmediately: aws.Bool(true), 245 } 246 247 if len(addOptions) > 0 { 248 modifyOpts.OptionsToInclude = addOptions 249 } 250 251 if len(removeOptions) > 0 { 252 modifyOpts.OptionsToRemove = removeOptions 253 } 254 255 log.Printf("[DEBUG] Modify DB Option Group: %s", modifyOpts) 256 _, err = rdsconn.ModifyOptionGroup(modifyOpts) 257 if err != nil { 258 return fmt.Errorf("Error modifying DB Option Group: %s", err) 259 } 260 d.SetPartial("option") 261 262 } 263 264 if arn, err := buildRDSOptionGroupARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region); err == nil { 265 if err := setTagsRDS(rdsconn, d, arn); err != nil { 266 return err 267 } else { 268 d.SetPartial("tags") 269 } 270 } 271 272 return resourceAwsDbOptionGroupRead(d, meta) 273 } 274 275 func resourceAwsDbOptionGroupDelete(d *schema.ResourceData, meta interface{}) error { 276 rdsconn := meta.(*AWSClient).rdsconn 277 278 deleteOpts := &rds.DeleteOptionGroupInput{ 279 OptionGroupName: aws.String(d.Id()), 280 } 281 282 log.Printf("[DEBUG] Delete DB Option Group: %#v", deleteOpts) 283 ret := resource.Retry(5*time.Minute, func() *resource.RetryError { 284 _, err := rdsconn.DeleteOptionGroup(deleteOpts) 285 if err != nil { 286 if awsErr, ok := err.(awserr.Error); ok { 287 if awsErr.Code() == "InvalidOptionGroupStateFault" { 288 log.Printf("[DEBUG] AWS believes the RDS Option Group is still in use, retrying") 289 return resource.RetryableError(awsErr) 290 } 291 } 292 return resource.NonRetryableError(err) 293 } 294 return nil 295 }) 296 if ret != nil { 297 return fmt.Errorf("Error Deleting DB Option Group: %s", ret) 298 } 299 return nil 300 } 301 302 func flattenOptionNames(configured []interface{}) ([]*string, error) { 303 var optionNames []*string 304 for _, pRaw := range configured { 305 data := pRaw.(map[string]interface{}) 306 optionNames = append(optionNames, aws.String(data["option_name"].(string))) 307 } 308 309 return optionNames, nil 310 } 311 312 func resourceAwsDbOptionHash(v interface{}) int { 313 var buf bytes.Buffer 314 m := v.(map[string]interface{}) 315 buf.WriteString(fmt.Sprintf("%s-", m["option_name"].(string))) 316 if _, ok := m["port"]; ok { 317 buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) 318 } 319 320 for _, oRaw := range m["option_settings"].(*schema.Set).List() { 321 o := oRaw.(map[string]interface{}) 322 buf.WriteString(fmt.Sprintf("%s-", o["name"].(string))) 323 buf.WriteString(fmt.Sprintf("%s-", o["value"].(string))) 324 } 325 326 for _, vpcRaw := range m["vpc_security_group_memberships"].(*schema.Set).List() { 327 buf.WriteString(fmt.Sprintf("%s-", vpcRaw.(string))) 328 } 329 330 for _, sgRaw := range m["db_security_group_memberships"].(*schema.Set).List() { 331 buf.WriteString(fmt.Sprintf("%s-", sgRaw.(string))) 332 } 333 return hashcode.String(buf.String()) 334 } 335 336 func buildRDSOptionGroupARN(identifier, partition, accountid, region string) (string, error) { 337 if partition == "" { 338 return "", fmt.Errorf("Unable to construct RDS Option Group ARN because of missing AWS partition") 339 } 340 if accountid == "" { 341 return "", fmt.Errorf("Unable to construct RDS Option Group ARN because of missing AWS Account ID") 342 } 343 arn := fmt.Sprintf("arn:%s:rds:%s:%s:og:%s", partition, region, accountid, identifier) 344 return arn, nil 345 } 346 347 func validateDbOptionGroupName(v interface{}, k string) (ws []string, errors []error) { 348 value := v.(string) 349 if !regexp.MustCompile(`^[a-z]`).MatchString(value) { 350 errors = append(errors, fmt.Errorf( 351 "first character of %q must be a letter", k)) 352 } 353 if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { 354 errors = append(errors, fmt.Errorf( 355 "only alphanumeric characters and hyphens allowed in %q", k)) 356 } 357 if regexp.MustCompile(`--`).MatchString(value) { 358 errors = append(errors, fmt.Errorf( 359 "%q cannot contain two consecutive hyphens", k)) 360 } 361 if regexp.MustCompile(`-$`).MatchString(value) { 362 errors = append(errors, fmt.Errorf( 363 "%q cannot end with a hyphen", k)) 364 } 365 if len(value) > 255 { 366 errors = append(errors, fmt.Errorf( 367 "%q cannot be greater than 255 characters", k)) 368 } 369 return 370 }