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