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_costoptimizationhub_preferences #36526

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
Loading