github.com/shvar/terraform@v0.6.9-0.20151215234924-3365cd2231df/builtin/providers/aws/resource_aws_db_parameter_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/hashicorp/terraform/helper/hashcode"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  
    15  	"github.com/aws/aws-sdk-go/aws"
    16  	"github.com/aws/aws-sdk-go/aws/awserr"
    17  	"github.com/aws/aws-sdk-go/service/iam"
    18  	"github.com/aws/aws-sdk-go/service/rds"
    19  )
    20  
    21  func resourceAwsDbParameterGroup() *schema.Resource {
    22  	return &schema.Resource{
    23  		Create: resourceAwsDbParameterGroupCreate,
    24  		Read:   resourceAwsDbParameterGroupRead,
    25  		Update: resourceAwsDbParameterGroupUpdate,
    26  		Delete: resourceAwsDbParameterGroupDelete,
    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  				ForceNew:     true,
    35  				Required:     true,
    36  				ValidateFunc: validateDbParamGroupName,
    37  			},
    38  			"family": &schema.Schema{
    39  				Type:     schema.TypeString,
    40  				Required: true,
    41  				ForceNew: true,
    42  			},
    43  			"description": &schema.Schema{
    44  				Type:     schema.TypeString,
    45  				Required: true,
    46  				ForceNew: true,
    47  			},
    48  			"parameter": &schema.Schema{
    49  				Type:     schema.TypeSet,
    50  				Optional: true,
    51  				ForceNew: false,
    52  				Elem: &schema.Resource{
    53  					Schema: map[string]*schema.Schema{
    54  						"name": &schema.Schema{
    55  							Type:     schema.TypeString,
    56  							Required: true,
    57  						},
    58  						"value": &schema.Schema{
    59  							Type:     schema.TypeString,
    60  							Required: true,
    61  						},
    62  						"apply_method": &schema.Schema{
    63  							Type:     schema.TypeString,
    64  							Optional: true,
    65  							Default:  "immediate",
    66  							// this parameter is not actually state, but a
    67  							// meta-parameter describing how the RDS API call
    68  							// to modify the parameter group should be made.
    69  							// Future reads of the resource from AWS don't tell
    70  							// us what we used for apply_method previously, so
    71  							// by squashing state to an empty string we avoid
    72  							// needing to do an update for every future run.
    73  							StateFunc: func(interface{}) string { return "" },
    74  						},
    75  					},
    76  				},
    77  				Set: resourceAwsDbParameterHash,
    78  			},
    79  
    80  			"tags": tagsSchema(),
    81  		},
    82  	}
    83  }
    84  
    85  func resourceAwsDbParameterGroupCreate(d *schema.ResourceData, meta interface{}) error {
    86  	rdsconn := meta.(*AWSClient).rdsconn
    87  	tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))
    88  
    89  	createOpts := rds.CreateDBParameterGroupInput{
    90  		DBParameterGroupName:   aws.String(d.Get("name").(string)),
    91  		DBParameterGroupFamily: aws.String(d.Get("family").(string)),
    92  		Description:            aws.String(d.Get("description").(string)),
    93  		Tags:                   tags,
    94  	}
    95  
    96  	log.Printf("[DEBUG] Create DB Parameter Group: %#v", createOpts)
    97  	_, err := rdsconn.CreateDBParameterGroup(&createOpts)
    98  	if err != nil {
    99  		return fmt.Errorf("Error creating DB Parameter Group: %s", err)
   100  	}
   101  
   102  	d.Partial(true)
   103  	d.SetPartial("name")
   104  	d.SetPartial("family")
   105  	d.SetPartial("description")
   106  	d.Partial(false)
   107  
   108  	d.SetId(*createOpts.DBParameterGroupName)
   109  	log.Printf("[INFO] DB Parameter Group ID: %s", d.Id())
   110  
   111  	return resourceAwsDbParameterGroupUpdate(d, meta)
   112  }
   113  
   114  func resourceAwsDbParameterGroupRead(d *schema.ResourceData, meta interface{}) error {
   115  	rdsconn := meta.(*AWSClient).rdsconn
   116  
   117  	describeOpts := rds.DescribeDBParameterGroupsInput{
   118  		DBParameterGroupName: aws.String(d.Id()),
   119  	}
   120  
   121  	describeResp, err := rdsconn.DescribeDBParameterGroups(&describeOpts)
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	if len(describeResp.DBParameterGroups) != 1 ||
   127  		*describeResp.DBParameterGroups[0].DBParameterGroupName != d.Id() {
   128  		return fmt.Errorf("Unable to find Parameter Group: %#v", describeResp.DBParameterGroups)
   129  	}
   130  
   131  	d.Set("name", describeResp.DBParameterGroups[0].DBParameterGroupName)
   132  	d.Set("family", describeResp.DBParameterGroups[0].DBParameterGroupFamily)
   133  	d.Set("description", describeResp.DBParameterGroups[0].Description)
   134  
   135  	// Only include user customized parameters as there's hundreds of system/default ones
   136  	describeParametersOpts := rds.DescribeDBParametersInput{
   137  		DBParameterGroupName: aws.String(d.Id()),
   138  		Source:               aws.String("user"),
   139  	}
   140  
   141  	describeParametersResp, err := rdsconn.DescribeDBParameters(&describeParametersOpts)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	d.Set("parameter", flattenParameters(describeParametersResp.Parameters))
   147  
   148  	paramGroup := describeResp.DBParameterGroups[0]
   149  	arn, err := buildRDSPGARN(d, meta)
   150  	if err != nil {
   151  		name := "<empty>"
   152  		if paramGroup.DBParameterGroupName != nil && *paramGroup.DBParameterGroupName != "" {
   153  			name = *paramGroup.DBParameterGroupName
   154  		}
   155  		log.Printf("[DEBUG] Error building ARN for DB Parameter Group, not setting Tags for Param Group %s", name)
   156  	} else {
   157  		d.Set("arn", arn)
   158  		resp, err := rdsconn.ListTagsForResource(&rds.ListTagsForResourceInput{
   159  			ResourceName: aws.String(arn),
   160  		})
   161  
   162  		if err != nil {
   163  			log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn)
   164  		}
   165  
   166  		var dt []*rds.Tag
   167  		if len(resp.TagList) > 0 {
   168  			dt = resp.TagList
   169  		}
   170  		d.Set("tags", tagsToMapRDS(dt))
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  func resourceAwsDbParameterGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   177  	rdsconn := meta.(*AWSClient).rdsconn
   178  
   179  	d.Partial(true)
   180  
   181  	if d.HasChange("parameter") {
   182  		o, n := d.GetChange("parameter")
   183  		if o == nil {
   184  			o = new(schema.Set)
   185  		}
   186  		if n == nil {
   187  			n = new(schema.Set)
   188  		}
   189  
   190  		os := o.(*schema.Set)
   191  		ns := n.(*schema.Set)
   192  
   193  		// Expand the "parameter" set to aws-sdk-go compat []rds.Parameter
   194  		parameters, err := expandParameters(ns.Difference(os).List())
   195  		if err != nil {
   196  			return err
   197  		}
   198  
   199  		if len(parameters) > 0 {
   200  			modifyOpts := rds.ModifyDBParameterGroupInput{
   201  				DBParameterGroupName: aws.String(d.Get("name").(string)),
   202  				Parameters:           parameters,
   203  			}
   204  
   205  			log.Printf("[DEBUG] Modify DB Parameter Group: %s", modifyOpts)
   206  			_, err = rdsconn.ModifyDBParameterGroup(&modifyOpts)
   207  			if err != nil {
   208  				return fmt.Errorf("Error modifying DB Parameter Group: %s", err)
   209  			}
   210  		}
   211  		d.SetPartial("parameter")
   212  	}
   213  
   214  	if arn, err := buildRDSPGARN(d, meta); err == nil {
   215  		if err := setTagsRDS(rdsconn, d, arn); err != nil {
   216  			return err
   217  		} else {
   218  			d.SetPartial("tags")
   219  		}
   220  	}
   221  
   222  	d.Partial(false)
   223  
   224  	return resourceAwsDbParameterGroupRead(d, meta)
   225  }
   226  
   227  func resourceAwsDbParameterGroupDelete(d *schema.ResourceData, meta interface{}) error {
   228  	stateConf := &resource.StateChangeConf{
   229  		Pending:    []string{"pending"},
   230  		Target:     "destroyed",
   231  		Refresh:    resourceAwsDbParameterGroupDeleteRefreshFunc(d, meta),
   232  		Timeout:    3 * time.Minute,
   233  		MinTimeout: 1 * time.Second,
   234  	}
   235  	_, err := stateConf.WaitForState()
   236  	return err
   237  }
   238  
   239  func resourceAwsDbParameterGroupDeleteRefreshFunc(
   240  	d *schema.ResourceData,
   241  	meta interface{}) resource.StateRefreshFunc {
   242  	rdsconn := meta.(*AWSClient).rdsconn
   243  
   244  	return func() (interface{}, string, error) {
   245  
   246  		deleteOpts := rds.DeleteDBParameterGroupInput{
   247  			DBParameterGroupName: aws.String(d.Id()),
   248  		}
   249  
   250  		if _, err := rdsconn.DeleteDBParameterGroup(&deleteOpts); err != nil {
   251  			rdserr, ok := err.(awserr.Error)
   252  			if !ok {
   253  				return d, "error", err
   254  			}
   255  
   256  			if rdserr.Code() != "DBParameterGroupNotFoundFault" {
   257  				return d, "error", err
   258  			}
   259  		}
   260  
   261  		return d, "destroyed", nil
   262  	}
   263  }
   264  
   265  func resourceAwsDbParameterHash(v interface{}) int {
   266  	var buf bytes.Buffer
   267  	m := v.(map[string]interface{})
   268  	buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
   269  	// Store the value as a lower case string, to match how we store them in flattenParameters
   270  	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["value"].(string))))
   271  
   272  	return hashcode.String(buf.String())
   273  }
   274  
   275  func buildRDSPGARN(d *schema.ResourceData, meta interface{}) (string, error) {
   276  	iamconn := meta.(*AWSClient).iamconn
   277  	region := meta.(*AWSClient).region
   278  	// An zero value GetUserInput{} defers to the currently logged in user
   279  	resp, err := iamconn.GetUser(&iam.GetUserInput{})
   280  	if err != nil {
   281  		return "", err
   282  	}
   283  	userARN := *resp.User.Arn
   284  	accountID := strings.Split(userARN, ":")[4]
   285  	arn := fmt.Sprintf("arn:aws:rds:%s:%s:pg:%s", region, accountID, d.Id())
   286  	return arn, nil
   287  }
   288  
   289  func validateDbParamGroupName(v interface{}, k string) (ws []string, errors []error) {
   290  	value := v.(string)
   291  	if !regexp.MustCompile(`^[0-9a-z-]+$`).MatchString(value) {
   292  		errors = append(errors, fmt.Errorf(
   293  			"only lowercase alphanumeric characters and hyphens allowed in %q", k))
   294  	}
   295  	if !regexp.MustCompile(`^[a-z]`).MatchString(value) {
   296  		errors = append(errors, fmt.Errorf(
   297  			"first character of %q must be a letter", k))
   298  	}
   299  	if regexp.MustCompile(`--`).MatchString(value) {
   300  		errors = append(errors, fmt.Errorf(
   301  			"%q cannot contain two consecutive hyphens", k))
   302  	}
   303  	if regexp.MustCompile(`-$`).MatchString(value) {
   304  		errors = append(errors, fmt.Errorf(
   305  			"%q cannot end with a hyphen", k))
   306  	}
   307  	if len(value) > 255 {
   308  		errors = append(errors, fmt.Errorf(
   309  			"%q cannot be greater than 255 characters", k))
   310  	}
   311  	return
   312  
   313  }