Skip to content

Commit

Permalink
Merge pull request #36526 from madhavvishnubhatta/f-aws_costoptimizat…
Browse files Browse the repository at this point in the history
…ionhub_preferences

New resource: aws_costoptimizationhub_preferences
  • Loading branch information
YakDriver authored Sep 12, 2024
2 parents 50a8f2c + 25cf1c1 commit ea4e6b9
Show file tree
Hide file tree
Showing 9 changed files with 572 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .changelog/36526.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_costoptimizationhub_preferences
```
2 changes: 1 addition & 1 deletion .ci/tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/hashicorp/terraform-provider-aws/tools
go 1.23.0

require (
github.com/YakDriver/tfproviderdocs v0.13.0
github.com/YakDriver/tfproviderdocs v0.14.0
github.com/client9/misspell v0.3.4
github.com/golangci/golangci-lint v1.60.3
github.com/hashicorp/copywrite v0.19.0
Expand Down
4 changes: 2 additions & 2 deletions .ci/tools/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJP
github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ=
github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton h1:KVBEgU3CJpmzLChnLiSuEyCuhGhcMt3eOST+7A+ckto=
github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/YakDriver/tfproviderdocs v0.13.0 h1:MTM88fRxOrF3GhMSrleu/IDsvBkOXP8pvOpjFHEJjm0=
github.com/YakDriver/tfproviderdocs v0.13.0/go.mod h1:jhtn0KyVjpEls7APqBDSZsPoS9IDMY89XfVaXf/mnA4=
github.com/YakDriver/tfproviderdocs v0.14.0 h1:YrZvNTL6D7Ow5Q1fSSdrDs0sfZEgvtXcHyR5f5r0F/k=
github.com/YakDriver/tfproviderdocs v0.14.0/go.mod h1:pse9nenVl0LY7sqJFfn92takFWhGm+aEcssMtrig/YI=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ func TestAccCostOptimizationHub_serial(t *testing.T) {
acctest.CtDisappears: testAccEnrollmentStatus_disappears,
"includeMemberAccounts": testAccEnrollmentStatus_includeMemberAccounts,
},
"Preferences": {
acctest.CtBasic: testAccPreferences_basic,
acctest.CtDisappears: testAccPreferences_disappears,
"memberAccountsDiscountVisibility": testAccPreferences_memberAccountsDiscountVisibility,
"savingsEstimationMode": testAccPreferences_savingsEstimationMode,
},
}

acctest.RunSerialTests2Levels(t, testCases, 0)
Expand Down
1 change: 1 addition & 0 deletions internal/service/costoptimizationhub/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ package costoptimizationhub
// Exports for use in tests only.
var (
ResourceEnrollmentStatus = newResourceEnrollmentStatus
ResourcePreferences = newResourcePreferences
)
247 changes: 247 additions & 0 deletions internal/service/costoptimizationhub/preferences.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package costoptimizationhub

import (
"context"
"errors"
"time"

"github.com/aws/aws-sdk-go-v2/service/costoptimizationhub"
awstypes "github.com/aws/aws-sdk-go-v2/service/costoptimizationhub/types"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/enum"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
"github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @FrameworkResource(name="Preferences")
func newResourcePreferences(_ context.Context) (resource.ResourceWithConfigure, error) {
r := &resourcePreferences{}

r.SetDefaultCreateTimeout(30 * time.Minute)
r.SetDefaultUpdateTimeout(30 * time.Minute)
r.SetDefaultDeleteTimeout(30 * time.Minute)

return r, nil
}

const (
ResNamePreferences = "Preferences"
)

type resourcePreferences struct {
framework.ResourceWithConfigure
framework.WithTimeouts
framework.WithImportByID
}

func (r *resourcePreferences) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "aws_costoptimizationhub_preferences"
}

func (r *resourcePreferences) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
names.AttrID: framework.IDAttribute(),
"member_account_discount_visibility": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString(string(awstypes.MemberAccountDiscountVisibilityAll)),
Validators: []validator.String{
enum.FrameworkValidate[awstypes.MemberAccountDiscountVisibility](),
},
},
"savings_estimation_mode": schema.StringAttribute{
Optional: true,
Computed: true,
Default: stringdefault.StaticString(string(awstypes.SavingsEstimationModeBeforeDiscounts)),
Validators: []validator.String{
enum.FrameworkValidate[awstypes.SavingsEstimationMode](),
},
},
},
}
}

func (r *resourcePreferences) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
conn := r.Meta().CostOptimizationHubClient(ctx)

var plan resourcePreferencesData
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

//Input for UpdatePreferences
in := &costoptimizationhub.UpdatePreferencesInput{}

if !plan.MemberAccountDiscountVisibility.IsNull() {
in.MemberAccountDiscountVisibility = awstypes.MemberAccountDiscountVisibility(plan.MemberAccountDiscountVisibility.ValueString())
}

if !plan.SavingsEstimationMode.IsNull() {
in.SavingsEstimationMode = awstypes.SavingsEstimationMode(plan.SavingsEstimationMode.ValueString())
}

out, err := conn.UpdatePreferences(ctx, in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.CostOptimizationHub, create.ErrActionCreating, ResNamePreferences, "UpdatePreferences", err),
err.Error(),
)
return
}
if out == nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.CostOptimizationHub, create.ErrActionCreating, ResNamePreferences, "UpdatePreferences", nil),
errors.New("empty out").Error(),
)
return
}

plan.ID = flex.StringValueToFramework(ctx, r.Meta().AccountID)
plan.MemberAccountDiscountVisibility = flex.StringValueToFramework(ctx, out.MemberAccountDiscountVisibility)
plan.SavingsEstimationMode = flex.StringValueToFramework(ctx, out.SavingsEstimationMode)

resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}

func (r *resourcePreferences) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
conn := r.Meta().CostOptimizationHubClient(ctx)

var state resourcePreferencesData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

out, err := findPreferences(ctx, conn)
if err != nil {
//Check if err is of type AccessDeniedException and contains the message "AWS account is not enrolled for recommendations"
//If that is the case, the Enrollment Status is inactive and hence this resource needs to be removed from state
if errs.IsAErrorMessageContains[*awstypes.AccessDeniedException](err, "AWS account is not enrolled for recommendations") {
resp.State.RemoveResource(ctx)
return
}

resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.CostOptimizationHub, create.ErrActionSetting, ResNamePreferences, state.ID.String(), err),
err.Error(),
)
return
}

state.ID = flex.StringValueToFramework(ctx, r.Meta().AccountID)
state.MemberAccountDiscountVisibility = flex.StringValueToFramework(ctx, out.MemberAccountDiscountVisibility)
state.SavingsEstimationMode = flex.StringValueToFramework(ctx, out.SavingsEstimationMode)

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *resourcePreferences) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
conn := r.Meta().CostOptimizationHubClient(ctx)

var plan, state resourcePreferencesData
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

if !plan.MemberAccountDiscountVisibility.Equal(state.MemberAccountDiscountVisibility) ||
!plan.SavingsEstimationMode.Equal(state.SavingsEstimationMode) {
in := &costoptimizationhub.UpdatePreferencesInput{}
if !plan.MemberAccountDiscountVisibility.IsNull() {
in.MemberAccountDiscountVisibility = awstypes.MemberAccountDiscountVisibility(plan.MemberAccountDiscountVisibility.ValueString())
}
if !plan.SavingsEstimationMode.IsNull() {
in.SavingsEstimationMode = awstypes.SavingsEstimationMode(plan.SavingsEstimationMode.ValueString())
}

out, err := conn.UpdatePreferences(ctx, in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.CostOptimizationHub, create.ErrActionCreating, ResNamePreferences, plan.ID.String(), err),
err.Error(),
)
return
}

if out == nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.CostOptimizationHub, create.ErrActionCreating, ResNamePreferences, plan.ID.String(), nil),
errors.New("empty out").Error(),
)
return
}

plan.ID = state.ID
plan.MemberAccountDiscountVisibility = flex.StringValueToFramework(ctx, out.MemberAccountDiscountVisibility)
plan.SavingsEstimationMode = flex.StringValueToFramework(ctx, out.SavingsEstimationMode)
}

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

// For this "Preferences" resource, deletion is just resetting the preferences back to the default values.
func (r *resourcePreferences) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
conn := r.Meta().CostOptimizationHubClient(ctx)

var state resourcePreferencesData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

in := &costoptimizationhub.UpdatePreferencesInput{
MemberAccountDiscountVisibility: awstypes.MemberAccountDiscountVisibilityAll,
SavingsEstimationMode: awstypes.SavingsEstimationModeBeforeDiscounts,
}

out, err := conn.UpdatePreferences(ctx, in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.CostOptimizationHub, create.ErrActionCreating, ResNamePreferences, "UpdatePreferences", err),
err.Error(),
)
return
}
if out == nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.CostOptimizationHub, create.ErrActionCreating, ResNamePreferences, "UpdatePreferences", nil),
errors.New("empty out").Error(),
)
return
}
}

func findPreferences(ctx context.Context, conn *costoptimizationhub.Client) (*costoptimizationhub.GetPreferencesOutput, error) {
in := &costoptimizationhub.GetPreferencesInput{}

out, err := conn.GetPreferences(ctx, in)
if err != nil {
return nil, err
}

return out, nil
}

func (r *resourcePreferences) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp)
}

type resourcePreferencesData struct {
ID types.String `tfsdk:"id"`
MemberAccountDiscountVisibility types.String `tfsdk:"member_account_discount_visibility"`
SavingsEstimationMode types.String `tfsdk:"savings_estimation_mode"`
}
Loading

0 comments on commit ea4e6b9

Please sign in to comment.