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

New Resource: aws_rds_reserved_instance #26025

Merged
merged 28 commits into from
Oct 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fa30027
rds: Amend consts
brittandeyoung Jul 28, 2022
5d7c863
rds: Amend find
brittandeyoung Jul 28, 2022
0685e2a
rds: New Resource aws_rds_reserved_instance
brittandeyoung Jul 28, 2022
80b5983
rds: Amend provider
brittandeyoung Jul 28, 2022
2eb9489
rds: Amend reserved_instance
brittandeyoung Jul 28, 2022
e4d5263
rds: Add aws_rds_reserved_instance documentation
brittandeyoung Jul 28, 2022
546f0e9
rds: Amend aws_rds_reserved_instance resource
brittandeyoung Jul 28, 2022
1649215
rds: Amend aws_rds_reserved_instance documentation
brittandeyoung Jul 28, 2022
f566cde
rds: Amend reserved_instance resource
brittandeyoung Jul 28, 2022
bee6927
rds: Amend reserved instance and docs
brittandeyoung Aug 10, 2022
3b7c71f
rds: Amend reserved instance resource
brittandeyoung Aug 10, 2022
18734d0
rds: Amend reserved instance resource
brittandeyoung Aug 10, 2022
95f00cb
rds: Update reserved_instance
brittandeyoung Sep 23, 2022
aa763a5
rds: Update reserved_instance
brittandeyoung Sep 26, 2022
c04e135
rds: reserved_instance change log
brittandeyoung Sep 26, 2022
276c8dc
rds: Add DataSourceReservedOffering
brittandeyoung Sep 29, 2022
518c459
rds: Add reserved_instance_test
brittandeyoung Sep 30, 2022
31823c6
rds: add reserved_instance_offering datasource docs
brittandeyoung Sep 30, 2022
111271b
rds: update reserved_instance docs
brittandeyoung Sep 30, 2022
c78ca91
rds: remove RDS from test function names reserved_instance
brittandeyoung Sep 30, 2022
0fda7a9
rds: Amend reserved instance test
brittandeyoung Sep 30, 2022
8dc3dc9
update 26025 change log
brittandeyoung Sep 30, 2022
b794006
rds: Amend reserved_instance_test
brittandeyoung Sep 30, 2022
02732f2
rds/reserved_instance: Fix nits
YakDriver Oct 7, 2022
080171e
docs/rds/reserved_instance: Update docs
YakDriver Oct 7, 2022
afd3c56
rds/reserved_instance: Add waiter, update optionals
YakDriver Oct 7, 2022
7e3a42b
rds/reserved_instance: Fix internal validation
YakDriver Oct 7, 2022
6b0cc97
docs/reserved_instance: Remove dead link
YakDriver Oct 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changelog/26025.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:new-resource
aws_rds_reserved_instance
```

```release-note:new-data-source
aws_rds_reserved_instance_offering
```
22 changes: 12 additions & 10 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,16 +824,17 @@ func New(_ context.Context) (*schema.Provider, error) {
"aws_ses_domain_identity": ses.DataSourceDomainIdentity(),
"aws_ses_email_identity": ses.DataSourceEmailIdentity(),

"aws_db_cluster_snapshot": rds.DataSourceClusterSnapshot(),
"aws_db_event_categories": rds.DataSourceEventCategories(),
"aws_db_instance": rds.DataSourceInstance(),
"aws_db_proxy": rds.DataSourceProxy(),
"aws_db_snapshot": rds.DataSourceSnapshot(),
"aws_db_subnet_group": rds.DataSourceSubnetGroup(),
"aws_rds_certificate": rds.DataSourceCertificate(),
"aws_rds_cluster": rds.DataSourceCluster(),
"aws_rds_engine_version": rds.DataSourceEngineVersion(),
"aws_rds_orderable_db_instance": rds.DataSourceOrderableInstance(),
"aws_db_cluster_snapshot": rds.DataSourceClusterSnapshot(),
"aws_db_event_categories": rds.DataSourceEventCategories(),
"aws_db_instance": rds.DataSourceInstance(),
"aws_db_proxy": rds.DataSourceProxy(),
"aws_db_snapshot": rds.DataSourceSnapshot(),
"aws_db_subnet_group": rds.DataSourceSubnetGroup(),
"aws_rds_certificate": rds.DataSourceCertificate(),
"aws_rds_cluster": rds.DataSourceCluster(),
"aws_rds_engine_version": rds.DataSourceEngineVersion(),
"aws_rds_orderable_db_instance": rds.DataSourceOrderableInstance(),
"aws_rds_reserved_instance_offering": rds.DataSourceReservedOffering(),

"aws_redshift_cluster": redshift.DataSourceCluster(),
"aws_redshift_cluster_credentials": redshift.DataSourceClusterCredentials(),
Expand Down Expand Up @@ -1868,6 +1869,7 @@ func New(_ context.Context) (*schema.Provider, error) {
"aws_rds_cluster_parameter_group": rds.ResourceClusterParameterGroup(),
"aws_rds_cluster_role_association": rds.ResourceClusterRoleAssociation(),
"aws_rds_global_cluster": rds.ResourceGlobalCluster(),
"aws_rds_reserved_instance": rds.ResourceReservedInstance(),

"aws_redshift_authentication_profile": redshift.ResourceAuthenticationProfile(),
"aws_redshift_cluster": redshift.ResourceCluster(),
Expand Down
10 changes: 10 additions & 0 deletions internal/service/rds/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,13 @@ func TimeoutAction_Values() []string {
const (
propagationTimeout = 2 * time.Minute
)

const (
ResNameTags = "Tags"
)

const (
ReservedInstanceStateActive = "active"
ReservedInstanceStateRetired = "retired"
ReservedInstanceStatePaymentPending = "payment-pending"
)
24 changes: 24 additions & 0 deletions internal/service/rds/find.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package rds

import (
"context"
"log"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -494,3 +495,26 @@ func findGlobalClusters(conn *rds.RDS, input *rds.DescribeGlobalClustersInput) (

return output, nil
}

// FindReservedDBInstanceByID returns matching ReservedDBInstance.
func FindReservedDBInstanceByID(ctx context.Context, conn *rds.RDS, id string) (*rds.ReservedDBInstance, error) {

input := &rds.DescribeReservedDBInstancesInput{
ReservedDBInstanceId: aws.String(id),
}

output, err := conn.DescribeReservedDBInstancesWithContext(ctx, input)

if tfawserr.ErrCodeEquals(err, rds.ErrCodeReservedDBInstanceNotFoundFault) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if output == nil || len(output.ReservedDBInstances) == 0 || output.ReservedDBInstances[0] == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output.ReservedDBInstances[0], nil
}
261 changes: 261 additions & 0 deletions internal/service/rds/reserved_instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
package rds

import (
"context"
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/create"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
"github.com/hashicorp/terraform-provider-aws/names"
)

const (
ResNameReservedInstance = "Reserved Instance"
)

func ResourceReservedInstance() *schema.Resource {
return &schema.Resource{
CreateContext: resourceReservedInstanceCreate,
ReadContext: resourceReservedInstanceRead,
UpdateContext: resourceReservedInstanceUpdate,
DeleteContext: resourceReservedInstanceDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(30 * time.Minute),
Update: schema.DefaultTimeout(10 * time.Minute),
Delete: schema.DefaultTimeout(1 * time.Minute),
},
Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"currency_code": {
Type: schema.TypeString,
Computed: true,
},
"db_instance_class": {
Type: schema.TypeString,
Computed: true,
},
"duration": {
Type: schema.TypeInt,
Computed: true,
},
"fixed_price": {
Type: schema.TypeFloat,
Computed: true,
},
"instance_count": {
Type: schema.TypeInt,
Optional: true,
ForceNew: true,
Default: 1,
},
"lease_id": {
Type: schema.TypeString,
Computed: true,
},
"multi_az": {
Type: schema.TypeBool,
Computed: true,
},
"offering_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"offering_type": {
Type: schema.TypeString,
Computed: true,
},
"product_description": {
Type: schema.TypeString,
Computed: true,
},
"recurring_charges": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"recurring_charge_amount": {
Type: schema.TypeInt,
Computed: true,
},
"recurring_charge_frequency": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
"reservation_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"start_time": {
Type: schema.TypeString,
Computed: true,
},
"state": {
Type: schema.TypeString,
Computed: true,
},
"usage_price": {
Type: schema.TypeFloat,
Computed: true,
},
"tags": tftags.TagsSchema(),
"tags_all": tftags.TagsSchemaComputed(),
},

CustomizeDiff: verify.SetTagsDiff,
}
}

func resourceReservedInstanceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).RDSConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{})))

input := &rds.PurchaseReservedDBInstancesOfferingInput{
ReservedDBInstancesOfferingId: aws.String(d.Get("offering_id").(string)),
}

if v, ok := d.Get("instance_count").(int); ok && v > 0 {
input.DBInstanceCount = aws.Int64(int64(d.Get("instance_count").(int)))
}

if v, ok := d.Get("reservation_id").(string); ok && v != "" {
input.ReservedDBInstanceId = aws.String(v)
}

if len(tags) > 0 {
input.Tags = Tags(tags.IgnoreAWS())
}

resp, err := conn.PurchaseReservedDBInstancesOfferingWithContext(ctx, input)
if err != nil {
return create.DiagError(names.RDS, create.ErrActionCreating, ResNameReservedInstance, fmt.Sprintf("offering_id: %s, reservation_id: %s", d.Get("offering_id").(string), d.Get("reservation_id").(string)), err)
}

d.SetId(aws.ToString(resp.ReservedDBInstance.ReservedDBInstanceId))

if err := waitReservedInstanceCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil {
return create.DiagError(names.RDS, create.ErrActionWaitingForCreation, ResNameReservedInstance, d.Id(), err)
}

return resourceReservedInstanceRead(ctx, d, meta)
}

func resourceReservedInstanceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).RDSConn
defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig
ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig

reservation, err := FindReservedDBInstanceByID(ctx, conn, d.Id())

if !d.IsNewResource() && tfresource.NotFound(err) {
create.LogNotFoundRemoveState(names.RDS, create.ErrActionReading, ResNameReservedInstance, d.Id())
d.SetId("")
return nil
}

if err != nil {
return create.DiagError(names.RDS, create.ErrActionReading, ResNameReservedInstance, d.Id(), err)
}

d.Set("arn", reservation.ReservedDBInstanceArn)
d.Set("currency_code", reservation.CurrencyCode)
d.Set("db_instance_class", reservation.DBInstanceClass)
d.Set("duration", reservation.Duration)
d.Set("fixed_price", reservation.FixedPrice)
d.Set("instance_count", reservation.DBInstanceCount)
d.Set("lease_id", reservation.LeaseId)
d.Set("multi_az", reservation.MultiAZ)
d.Set("offering_id", reservation.ReservedDBInstancesOfferingId)
d.Set("offering_type", reservation.OfferingType)
d.Set("product_description", reservation.ProductDescription)
d.Set("recurring_charges", flattenRecurringCharges(reservation.RecurringCharges))
d.Set("reservation_id", reservation.ReservedDBInstanceId)
d.Set("start_time", (reservation.StartTime).Format(time.RFC3339))
d.Set("state", reservation.State)
d.Set("usage_price", reservation.UsagePrice)

tags, err := ListTags(conn, aws.ToString(reservation.ReservedDBInstanceArn))
tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig)

if err != nil {
return create.DiagError(names.CE, create.ErrActionReading, ResNameTags, d.Id(), err)
}

//lintignore:AWSR002
if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil {
return create.DiagError(names.CE, create.ErrActionUpdating, ResNameTags, d.Id(), err)
}

if err := d.Set("tags_all", tags.Map()); err != nil {
return create.DiagError(names.CE, create.ErrActionUpdating, ResNameTags, d.Id(), err)
}

return nil
}

func resourceReservedInstanceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
conn := meta.(*conns.AWSClient).RDSConn

if d.HasChange("tags") {
o, n := d.GetChange("tags")

if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil {
return create.DiagError(names.RDS, create.ErrActionUpdating, ResNameTags, d.Id(), err)
}
}

if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")

if err := UpdateTags(conn, d.Get("arn").(string), o, n); err != nil {
return create.DiagError(names.RDS, create.ErrActionUpdating, ResNameTags, d.Id(), err)
}
}

return resourceReservedInstanceRead(ctx, d, meta)
}

func resourceReservedInstanceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
// Reservations cannot be deleted. Removing from state.
log.Printf("[DEBUG] %s %s cannot be deleted. Removing from state.: %s", names.RDS, ResNameReservedInstance, d.Id())

return nil
}

func flattenRecurringCharges(recurringCharges []*rds.RecurringCharge) []interface{} {
if len(recurringCharges) == 0 {
return []interface{}{}
}

var rawRecurringCharges []interface{}
for _, recurringCharge := range recurringCharges {
rawRecurringCharge := map[string]interface{}{
"recurring_charge_amount": recurringCharge.RecurringChargeAmount,
"recurring_charge_frequency": aws.ToString(recurringCharge.RecurringChargeFrequency),
}

rawRecurringCharges = append(rawRecurringCharges, rawRecurringCharge)
}

return rawRecurringCharges
}
Loading