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