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