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