github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_db_option_group.go (about)

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