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

f/ add support for Neptune Serverless #27763

3 changes: 3 additions & 0 deletions .changelog/27763.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_neptune_cluster: Add `serverless_v2_scaling_configuration` block in support of [Neptune Serverless](https://docs.aws.amazon.com/neptune/latest/userguide/neptune-serverless.html)
```
170 changes: 100 additions & 70 deletions internal/service/neptune/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const (
cloudWatchLogsExportsAudit = "audit"

DefaultPort = 8182

ServerlessMinNCUs = 2.5
ServerlessMaxNCUs = 128.0
)

func ResourceCluster() *schema.Resource {
Expand All @@ -46,45 +49,38 @@ func ResourceCluster() *schema.Resource {
},

Schema: map[string]*schema.Schema{

// allow_major_version_upgrade is used to indicate whether upgrades between different major versions
// are allowed.
"allow_major_version_upgrade": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},

// apply_immediately is used to determine when the update modifications
// take place.
"apply_immediately": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},

"arn": {
Type: schema.TypeString,
Computed: true,
},

"availability_zones": {
Type: schema.TypeSet,
MaxItems: 3,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
ForceNew: true,
Computed: true,
Set: schema.HashString,
},

"backup_retention_period": {
Type: schema.TypeInt,
Optional: true,
Default: 1,
ValidateFunc: validation.IntAtMost(35),
},

"cluster_identifier": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -93,37 +89,30 @@ func ResourceCluster() *schema.Resource {
ConflictsWith: []string{"cluster_identifier_prefix"},
ValidateFunc: validIdentifier,
},

"cluster_identifier_prefix": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validIdentifierPrefix,
},

"cluster_members": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
Set: schema.HashString,
},

"cluster_resource_id": {
Type: schema.TypeString,
Computed: true,
},

"copy_tags_to_snapshot": {
Type: schema.TypeBool,
Optional: true,
},

"endpoint": {
Type: schema.TypeString,
Computed: true,
"deletion_protection": {
Type: schema.TypeBool,
Optional: true,
},

"enable_cloudwatch_logs_exports": {
Type: schema.TypeSet,
Optional: true,
Expand All @@ -133,23 +122,23 @@ func ResourceCluster() *schema.Resource {
cloudWatchLogsExportsAudit,
}, false),
},
Set: schema.HashString,
},

"endpoint": {
Type: schema.TypeString,
Computed: true,
},
"engine": {
Type: schema.TypeString,
Optional: true,
Default: "neptune",
ForceNew: true,
ValidateFunc: validEngine(),
},

"engine_version": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"final_snapshot_identifier": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -168,62 +157,52 @@ func ResourceCluster() *schema.Resource {
return
},
},

"hosted_zone_id": {
Type: schema.TypeString,
Computed: true,
},

"iam_database_authentication_enabled": {
Type: schema.TypeBool,
Optional: true,
},
"iam_roles": {
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: verify.ValidARN,
},
Set: schema.HashString,
},

"iam_database_authentication_enabled": {
Type: schema.TypeBool,
Optional: true,
},

"kms_key_arn": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: verify.ValidARN,
},

"neptune_subnet_group_name": {
"neptune_cluster_parameter_group_name": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
Default: "default.neptune1",
},

"neptune_cluster_parameter_group_name": {
"neptune_subnet_group_name": {
Type: schema.TypeString,
Optional: true,
Default: "default.neptune1",
ForceNew: true,
Computed: true,
},

"port": {
Type: schema.TypeInt,
Optional: true,
Default: DefaultPort,
ForceNew: true,
},

"preferred_backup_window": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: verify.ValidOnceADayWindowFormat,
},

"preferred_maintenance_window": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -236,48 +215,61 @@ func ResourceCluster() *schema.Resource {
},
ValidateFunc: verify.ValidOnceAWeekWindowFormat,
},

"reader_endpoint": {
Type: schema.TypeString,
Computed: true,
},

"replication_source_identifier": {
Type: schema.TypeString,
Optional: true,
},

"storage_encrypted": {
Type: schema.TypeBool,
"serverless_v2_scaling_configuration": {
Type: schema.TypeList,
Optional: true,
Default: false,
ForceNew: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"max_capacity": {
Type: schema.TypeFloat,
Optional: true,
Default: ServerlessMaxNCUs,
// Maximum capacity is 128 NCUs
// see: https://docs.aws.amazon.com/neptune/latest/userguide/neptune-serverless-capacity-scaling.html
ValidateFunc: validation.FloatAtMost(ServerlessMaxNCUs),
},
"min_capacity": {
Type: schema.TypeFloat,
Optional: true,
Default: ServerlessMinNCUs,
// Minimum capacity is 2.5 NCUs
// see: https://docs.aws.amazon.com/neptune/latest/userguide/neptune-serverless-capacity-scaling.html
ValidateFunc: validation.FloatAtLeast(ServerlessMinNCUs),
},
},
},
},

"skip_final_snapshot": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},

"snapshot_identifier": {
Type: schema.TypeString,
Optional: true,
},

"storage_encrypted": {
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),

"vpc_security_group_ids": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"deletion_protection": {
Type: schema.TypeBool,
Optional: true,
},
},

Expand Down Expand Up @@ -307,23 +299,27 @@ func resourceClusterCreate(d *schema.ResourceData, meta interface{}) error {
}
}

serverlessConfiguration := expandServerlessConfiguration(d.Get("serverless_v2_scaling_configuration").([]interface{}))

createDbClusterInput := &neptune.CreateDBClusterInput{
DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
CopyTagsToSnapshot: aws.Bool(d.Get("copy_tags_to_snapshot").(bool)),
Engine: aws.String(d.Get("engine").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
StorageEncrypted: aws.Bool(d.Get("storage_encrypted").(bool)),
DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)),
Tags: Tags(tags.IgnoreAWS()),
DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
CopyTagsToSnapshot: aws.Bool(d.Get("copy_tags_to_snapshot").(bool)),
Engine: aws.String(d.Get("engine").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
StorageEncrypted: aws.Bool(d.Get("storage_encrypted").(bool)),
DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)),
Tags: Tags(tags.IgnoreAWS()),
ServerlessV2ScalingConfiguration: serverlessConfiguration,
}
restoreDBClusterFromSnapshotInput := &neptune.RestoreDBClusterFromSnapshotInput{
DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
CopyTagsToSnapshot: aws.Bool(d.Get("copy_tags_to_snapshot").(bool)),
Engine: aws.String(d.Get("engine").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
SnapshotIdentifier: aws.String(d.Get("snapshot_identifier").(string)),
DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)),
Tags: Tags(tags.IgnoreAWS()),
DBClusterIdentifier: aws.String(d.Get("cluster_identifier").(string)),
CopyTagsToSnapshot: aws.Bool(d.Get("copy_tags_to_snapshot").(bool)),
Engine: aws.String(d.Get("engine").(string)),
Port: aws.Int64(int64(d.Get("port").(int))),
SnapshotIdentifier: aws.String(d.Get("snapshot_identifier").(string)),
DeletionProtection: aws.Bool(d.Get("deletion_protection").(bool)),
Tags: Tags(tags.IgnoreAWS()),
ServerlessV2ScalingConfiguration: serverlessConfiguration,
}

if attr := d.Get("availability_zones").(*schema.Set); attr.Len() > 0 {
Expand Down Expand Up @@ -480,6 +476,19 @@ func resourceClusterRead(d *schema.ResourceData, meta interface{}) error {
return flattenClusterResource(d, meta, dbc)
}

func flattenServerlessV2ScalingConfigurationInfo(serverlessConfig *neptune.ServerlessV2ScalingConfigurationInfo) []map[string]interface{} {
if serverlessConfig == nil {
return []map[string]interface{}{}
}

m := map[string]interface{}{
"min_capacity": aws.Float64Value(serverlessConfig.MinCapacity),
"max_capacity": aws.Float64Value(serverlessConfig.MaxCapacity),
}

return []map[string]interface{}{m}
}

func flattenClusterResource(d *schema.ResourceData, meta interface{}, dbc *neptune.DBCluster) error {
conn := meta.(*conns.AWSClient).NeptuneConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
Expand Down Expand Up @@ -514,6 +523,10 @@ func flattenClusterResource(d *schema.ResourceData, meta interface{}, dbc *neptu
d.Set("storage_encrypted", dbc.StorageEncrypted)
d.Set("deletion_protection", dbc.DeletionProtection)

if err := d.Set("serverless_v2_scaling_configuration", flattenServerlessV2ScalingConfigurationInfo(dbc.ServerlessV2ScalingConfiguration)); err != nil {
return fmt.Errorf("error setting serverless_v2_scaling_configuration: %w", err)
}

var sg []string
for _, g := range dbc.VpcSecurityGroups {
sg = append(sg, aws.StringValue(g.VpcSecurityGroupId))
Expand Down Expand Up @@ -642,6 +655,11 @@ func resourceClusterUpdate(d *schema.ResourceData, meta interface{}) error {
requestUpdate = true
}

if d.HasChange("serverless_v2_scaling_configuration") {
req.ServerlessV2ScalingConfiguration = expandServerlessConfiguration(d.Get("serverless_v2_scaling_configuration").([]interface{}))
requestUpdate = true
}

if requestUpdate {
err := resource.Retry(5*time.Minute, func() *resource.RetryError {
_, err := conn.ModifyDBCluster(req)
Expand Down Expand Up @@ -778,3 +796,15 @@ func removeIAMRoleFromCluster(clusterIdentifier string, roleArn string, conn *ne
_, err := conn.RemoveRoleFromDBCluster(params)
return err
}

func expandServerlessConfiguration(l []interface{}) *neptune.ServerlessV2ScalingConfiguration {
if len(l) == 0 {
return nil
}

tfMap := l[0].(map[string]interface{})
return &neptune.ServerlessV2ScalingConfiguration{
MinCapacity: aws.Float64(tfMap["min_capacity"].(float64)),
MaxCapacity: aws.Float64(tfMap["max_capacity"].(float64)),
}
}
Loading