Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resource - Neptune: aws_neptune_parameter_group #4724

Merged
merged 15 commits into from
Jun 13, 2018
3 changes: 3 additions & 0 deletions aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import (
"github.com/aws/aws-sdk-go/service/lightsail"
"github.com/aws/aws-sdk-go/service/mediastore"
"github.com/aws/aws-sdk-go/service/mq"
"github.com/aws/aws-sdk-go/service/neptune"
"github.com/aws/aws-sdk-go/service/opsworks"
"github.com/aws/aws-sdk-go/service/organizations"
"github.com/aws/aws-sdk-go/service/rds"
Expand Down Expand Up @@ -226,6 +227,7 @@ type AWSClient struct {
appsyncconn *appsync.AppSync
lexmodelconn *lexmodelbuildingservice.LexModelBuildingService
budgetconn *budgets.Budgets
neptuneconn *neptune.Neptune
}

func (c *AWSClient) S3() *s3.S3 {
Expand Down Expand Up @@ -494,6 +496,7 @@ func (c *Config) Client() (interface{}, error) {
client.lexmodelconn = lexmodelbuildingservice.New(sess)
client.lightsailconn = lightsail.New(sess)
client.mqconn = mq.New(sess)
client.neptuneconn = neptune.New(sess)
client.opsworksconn = opsworks.New(sess)
client.organizationsconn = organizations.New(sess)
client.r53conn = route53.New(r53Sess)
Expand Down
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ func Provider() terraform.ResourceProvider {
"aws_nat_gateway": resourceAwsNatGateway(),
"aws_network_acl": resourceAwsNetworkAcl(),
"aws_default_network_acl": resourceAwsDefaultNetworkAcl(),
"aws_neptune_parameter_group": resourceAwsNeptuneParameterGroup(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new resource is missing documentation:

  • A new documentation page: website/docs/r/neptune_parameter_group.html.markdown
  • A sidebar link in website/aws.erb

"aws_network_acl_rule": resourceAwsNetworkAclRule(),
"aws_network_interface": resourceAwsNetworkInterface(),
"aws_network_interface_attachment": resourceAwsNetworkInterfaceAttachment(),
Expand Down
280 changes: 280 additions & 0 deletions aws/resource_aws_neptune_parameter_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package aws

import (
"bytes"
"fmt"
"log"
"strings"
"time"

"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/neptune"
)

// We can only modify 20 parameters at a time, so walk them until
// we've got them all.
const maxParams = 20

func resourceAwsNeptuneParameterGroup() *schema.Resource {
return &schema.Resource{
Create: resourceAwsNeptuneParameterGroupCreate,
Read: resourceAwsNeptuneParameterGroupRead,
Update: resourceAwsNeptuneParameterGroupUpdate,
Delete: resourceAwsNeptuneParameterGroupDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
ForceNew: true,
Required: true,
StateFunc: func(val interface{}) string {
return strings.ToLower(val.(string))
},
},
"family": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "Managed by Terraform",
},
"parameter": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"value": {
Type: schema.TypeString,
Required: true,
},
"apply_method": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This argument was not documented originally and will be added post merge. Also adding validation:

ValidateFunc: validation.StringInSlice([]string{
	neptune.ApplyMethodImmediate,
	neptune.ApplyMethodPendingReboot,
}, false),

Type: schema.TypeString,
Optional: true,
Default: "immediate",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be safer to default to pending-reboot as that is applicable to both static and dynamic parameters.

// this parameter is not actually state, but a
// meta-parameter describing how the RDS API call
// to modify the parameter group should be made.
// Future reads of the resource from AWS don't tell
// us what we used for apply_method previously, so
// by squashing state to an empty string we avoid
// needing to do an update for every future run.
StateFunc: func(interface{}) string { return "" },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure where this StateFunc and the comment came from, but the current Neptune API does properly return ApplyMethod so we can set it in the Terraform state without modification.

},
},
},
Set: resourceAwsNeptuneParameterHash,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It turns out that this custom Set function was extraneous as we want to have apply_method computed as a difference if it changed.

},
},
}
}

func resourceAwsNeptuneParameterGroupCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).neptuneconn

createOpts := neptune.CreateDBParameterGroupInput{
DBParameterGroupName: aws.String(d.Get("name").(string)),
DBParameterGroupFamily: aws.String(d.Get("family").(string)),
Description: aws.String(d.Get("description").(string)),
}

log.Printf("[DEBUG] Create Neptune Parameter Group: %#v", createOpts)
resp, err := conn.CreateDBParameterGroup(&createOpts)
if err != nil {
return fmt.Errorf("Error creating Neptune Parameter Group: %s", err)
}

d.Partial(true)
d.SetPartial("name")
d.SetPartial("family")
d.SetPartial("description")
d.Partial(false)

d.SetId(*resp.DBParameterGroup.DBParameterGroupName)
log.Printf("[INFO] Neptune Parameter Group ID: %s", d.Id())

return resourceAwsNeptuneParameterGroupUpdate(d, meta)
}

func resourceAwsNeptuneParameterGroupRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).neptuneconn

describeOpts := neptune.DescribeDBParameterGroupsInput{
DBParameterGroupName: aws.String(d.Id()),
}

describeResp, err := conn.DescribeDBParameterGroups(&describeOpts)
if err != nil {
if isAWSErr(err, neptune.ErrCodeDBParameterGroupNotFoundFault, "") {
log.Printf("[WARN] Neptune Parameter Group (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
return err
}

if describeResp == nil {
return fmt.Errorf("Unable to get Describe Response for Neptune Parameter Group (%s)", d.Id())
}

if len(describeResp.DBParameterGroups) != 1 ||
*describeResp.DBParameterGroups[0].DBParameterGroupName != d.Id() {
return fmt.Errorf("Unable to find Parameter Group: %#v", describeResp.DBParameterGroups)
}

d.Set("name", describeResp.DBParameterGroups[0].DBParameterGroupName)
d.Set("family", describeResp.DBParameterGroups[0].DBParameterGroupFamily)
d.Set("description", describeResp.DBParameterGroups[0].Description)

// Only include user customized parameters as there's hundreds of system/default ones
describeParametersOpts := neptune.DescribeDBParametersInput{
DBParameterGroupName: aws.String(d.Id()),
Source: aws.String("user"),
}

var parameters []*neptune.Parameter
err = conn.DescribeDBParametersPages(&describeParametersOpts,
func(describeParametersResp *neptune.DescribeDBParametersOutput, lastPage bool) bool {
parameters = append(parameters, describeParametersResp.Parameters...)
return !lastPage
})
if err != nil {
return err
}

if err := d.Set("parameter", flattenNeptuneParameters(parameters)); err != nil {
return fmt.Errorf("error setting parameter: %s", err)
}

return nil
}

func resourceAwsNeptuneParameterGroupUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).neptuneconn

d.Partial(true)

if d.HasChange("parameter") {
o, n := d.GetChange("parameter")
if o == nil {
o = new(schema.Set)
}
if n == nil {
n = new(schema.Set)
}

os := o.(*schema.Set)
ns := n.(*schema.Set)

toRemove, err := expandNeptuneParameters(os.Difference(ns).List())
if err != nil {
return err
}

log.Printf("[DEBUG] Parameters to remove: %#v", toRemove)

toAdd, err := expandNeptuneParameters(ns.Difference(os).List())
if err != nil {
return err
}

log.Printf("[DEBUG] Parameters to add: %#v", toAdd)

for len(toRemove) > 0 {
paramsToModify := make([]*neptune.Parameter, 0)
if len(toRemove) <= maxParams {
paramsToModify, toRemove = toRemove[:], nil
} else {
paramsToModify, toRemove = toRemove[:maxParams], toRemove[maxParams:]
}
resetOpts := neptune.ResetDBParameterGroupInput{
DBParameterGroupName: aws.String(d.Get("name").(string)),
Parameters: paramsToModify,
}

log.Printf("[DEBUG] Reset Neptune Parameter Group: %s", resetOpts)
err := resource.Retry(30*time.Second, func() *resource.RetryError {
_, err = conn.ResetDBParameterGroup(&resetOpts)
if err != nil {
if isAWSErr(err, "InvalidDBParameterGroupState", " has pending changes") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})
if err != nil {
return fmt.Errorf("Error resetting Neptune Parameter Group: %s", err)
}
}

for len(toAdd) > 0 {
paramsToModify := make([]*neptune.Parameter, 0)
if len(toAdd) <= maxParams {
paramsToModify, toAdd = toAdd[:], nil
} else {
paramsToModify, toAdd = toAdd[:maxParams], toAdd[maxParams:]
}
modifyOpts := neptune.ModifyDBParameterGroupInput{
DBParameterGroupName: aws.String(d.Get("name").(string)),
Parameters: paramsToModify,
}

log.Printf("[DEBUG] Modify Neptune Parameter Group: %s", modifyOpts)
_, err = conn.ModifyDBParameterGroup(&modifyOpts)
if err != nil {
return fmt.Errorf("Error modifying Neptune Parameter Group: %s", err)
}
}

d.SetPartial("parameter")
}

d.Partial(false)

return resourceAwsNeptuneParameterGroupRead(d, meta)
}

func resourceAwsNeptuneParameterGroupDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).neptuneconn

return resource.Retry(3*time.Minute, func() *resource.RetryError {
deleteOpts := neptune.DeleteDBParameterGroupInput{
DBParameterGroupName: aws.String(d.Id()),
}
_, err := conn.DeleteDBParameterGroup(&deleteOpts)
if err != nil {
if isAWSErr(err, neptune.ErrCodeDBParameterGroupNotFoundFault, "") {
return nil
}
if isAWSErr(err, neptune.ErrCodeInvalidDBParameterGroupStateFault, "") {
return resource.RetryableError(err)
}
return resource.NonRetryableError(err)
}
return nil
})
}

func resourceAwsNeptuneParameterHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
// Store the value as a lower case string, to match how we store them in flattenParameters
buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["value"].(string))))

return hashcode.String(buf.String())
}
Loading