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