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  }