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