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