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