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