From 98e27cadd86a90f60671b0b67a1b69ca5251100b Mon Sep 17 00:00:00 2001 From: Andy Cheung Date: Mon, 15 Apr 2024 09:45:10 -0400 Subject: [PATCH 1/2] budgets data source --- generator.yaml | 30 +- go.mod | 2 + vantage/budget_resource_model.go | 98 + vantage/budgets_data_source.go | 74 + .../budgets_data_source_gen.go | 1833 +++++++++++++++++ .../resource_budget/budget_resource_gen.go | 1427 +++++++++++++ 6 files changed, 3463 insertions(+), 1 deletion(-) create mode 100644 vantage/budget_resource_model.go create mode 100644 vantage/budgets_data_source.go create mode 100644 vantage/datasource_budgets/budgets_data_source_gen.go create mode 100644 vantage/resource_budget/budget_resource_gen.go diff --git a/generator.yaml b/generator.yaml index 77ad866..cd2c838 100644 --- a/generator.yaml +++ b/generator.yaml @@ -22,7 +22,26 @@ resources: description: The token of the report alert aliases: anomaly_notification_token: token - + budget: + create: + path: /budgets + method: POST + read: + path: /budgets/{budget_token} + method: GET + update: + path: /budgets/{budget_token} + method: PUT + delete: + path: /budgets/{budget_token} + method: DELETE + schema: + attributes: + overrides: + token: + description: The token of the budget + aliases: + budget_token: token business_metric: create: path: /business_metrics @@ -44,6 +63,15 @@ resources: aliases: business_metric_token: token data_sources: + budgets: + read: + path: /budgets + method: GET + schema: + ignores: + - limit + - links + - page business_metrics: read: path: /business_metrics diff --git a/go.mod b/go.mod index d196ce5..1419b6f 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,8 @@ require ( github.com/vantage-sh/vantage-go v0.0.19 ) +replace github.com/vantage-sh/vantage-go v0.0.19 => ../vantage-go + require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect diff --git a/vantage/budget_resource_model.go b/vantage/budget_resource_model.go new file mode 100644 index 0000000..945ffcc --- /dev/null +++ b/vantage/budget_resource_model.go @@ -0,0 +1,98 @@ +package vantage + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + resource_budget "github.com/vantage-sh/terraform-provider-vantage/vantage/resource_budget" + modelsv2 "github.com/vantage-sh/vantage-go/vantagev2/models" +) + +// let the budget model defined in the resource be the common model for +// both the data source and the resource +type budgetModel resource_budget.BudgetModel + +type budgetPerformanceModel struct { + Actual types.String `tfsdk:"actual"` + Amount types.String `tfsdk:"amount"` + Date types.String `tfsdk:"budget"` +} + +type budgetPeriodModel struct { + Amount types.String `tfsdk:"amount"` + EndAt types.String `tfsdk:"end_at"` + StartAt types.String `tfsdk:"start_at"` +} + +// type budgetResourceModel struct {} +func applyBudgetPayload(ctx context.Context, src *modelsv2.Budget, dst *budgetModel) diag.Diagnostics { + dst.Token = types.StringValue(src.Token) + dst.CreatedAt = types.StringValue(src.CreatedAt) + dst.Name = types.StringValue(src.Name) + dst.UserToken = types.StringValue(src.UserToken) + dst.WorkspaceToken = types.StringValue(src.WorkspaceToken) + dst.CostReportToken = types.StringValue(src.CostReportToken) + + if src.BudgetAlertTokens != nil { + budgetAlertTokens, diag := types.ListValueFrom(ctx, types.StringType, src.BudgetAlertTokens) + if diag.HasError() { + return diag + } + dst.BudgetAlertTokens = budgetAlertTokens + } + + if src.Performance != nil { + perfs := make([]budgetPerformanceModel, 0, len(src.Performance)) + for _, p := range src.Performance { + performance := budgetPerformanceModel{ + Actual: types.StringValue(p.Actual), + Amount: types.StringValue(p.Amount), + Date: types.StringValue(p.Date), + } + perfs = append(perfs, performance) + } + + l, d := types.ListValueFrom( + ctx, + types.ObjectType{AttrTypes: map[string]attr.Type{ + "actual": types.StringType, + "amount": types.StringType, + "date": types.StringType, + }}, + perfs, + ) + if d.HasError() { + return d + } + dst.Performance = l + } + + if src.Periods != nil { + periods := make([]budgetPeriodModel, 0, len(src.Periods)) + for _, p := range src.Periods { + period := budgetPeriodModel{ + Amount: types.StringValue(p.Amount), + EndAt: types.StringValue(p.EndAt), + StartAt: types.StringValue(p.StartAt), + } + periods = append(periods, period) + } + + l, d := types.ListValueFrom( + ctx, + types.ObjectType{AttrTypes: map[string]attr.Type{ + "amount": types.StringType, + "end": types.StringType, + "start": types.StringType, + }}, + periods, + ) + if d.HasError() { + return d + } + dst.Periods = l + } + return nil +} diff --git a/vantage/budgets_data_source.go b/vantage/budgets_data_source.go new file mode 100644 index 0000000..01e348e --- /dev/null +++ b/vantage/budgets_data_source.go @@ -0,0 +1,74 @@ +package vantage + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/vantage-sh/terraform-provider-vantage/vantage/datasource_budgets" + budgetsv2 "github.com/vantage-sh/vantage-go/vantagev2/vantage/budgets" +) + +var _ datasource.DataSource = (*budgetsDataSource)(nil) +var _ datasource.DataSourceWithConfigure = (*budgetsDataSource)(nil) + +func NewBudgetsDataSource() datasource.DataSource { + return &budgetsDataSource{} +} + +type budgetsDataSource struct { + client *Client +} + +type budgetsDataSourceModel struct { + Budgets []budgetModel `tfsdk:"budgets"` +} + +func (d *budgetsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + d.client = req.ProviderData.(*Client) +} +func (d *budgetsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_budgets" +} + +func (d *budgetsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = datasource_budgets.BudgetsDataSourceSchema(ctx) +} + +func (d *budgetsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data budgetsDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := budgetsv2.NewGetBudgetsParams() + out, err := d.client.V2.Budgets.GetBudgets(params, d.client.Auth) + if err != nil { + resp.Diagnostics.AddError( + "Unable to Get Vantage Budgets", + err.Error(), + ) + return + } + budgets := []budgetModel{} + for _, budget := range out.Payload.Budgets { + model := budgetModel{} + diag := applyBudgetPayload(ctx, budget, &model) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + budgets = append(budgets, model) + } + + data.Budgets = budgets + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/vantage/datasource_budgets/budgets_data_source_gen.go b/vantage/datasource_budgets/budgets_data_source_gen.go new file mode 100644 index 0000000..292005d --- /dev/null +++ b/vantage/datasource_budgets/budgets_data_source_gen.go @@ -0,0 +1,1833 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package datasource_budgets + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func BudgetsDataSourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "budgets": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "budget_alert_tokens": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + Description: "The tokens of the BudgetAlerts associated with the Budget.", + MarkdownDescription: "The tokens of the BudgetAlerts associated with the Budget.", + }, + "cost_report_token": schema.StringAttribute{ + Computed: true, + Description: "The token of the Report associated with the Budget.", + MarkdownDescription: "The token of the Report associated with the Budget.", + }, + "created_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "The name of the Budget.", + MarkdownDescription: "The name of the Budget.", + }, + "performance": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "actual": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + "amount": schema.StringAttribute{ + Computed: true, + Description: "The amount of the Budget Period as a string to ensure precision.", + MarkdownDescription: "The amount of the Budget Period as a string to ensure precision.", + }, + "date": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + }, + CustomType: PerformanceType{ + ObjectType: types.ObjectType{ + AttrTypes: PerformanceValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + Description: "The historical performance of the Budget.", + MarkdownDescription: "The historical performance of the Budget.", + }, + "periods": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "amount": schema.StringAttribute{ + Computed: true, + Description: "The amount of the Budget Period as a string to ensure precision.", + MarkdownDescription: "The amount of the Budget Period as a string to ensure precision.", + }, + "end_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + "start_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + }, + CustomType: PeriodsType{ + ObjectType: types.ObjectType{ + AttrTypes: PeriodsValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + Description: "The budget periods associated with the Budget.", + MarkdownDescription: "The budget periods associated with the Budget.", + }, + "token": schema.StringAttribute{ + Computed: true, + }, + "user_token": schema.StringAttribute{ + Computed: true, + Description: "The token for the User who created this Budget.", + MarkdownDescription: "The token for the User who created this Budget.", + }, + "workspace_token": schema.StringAttribute{ + Computed: true, + Description: "The token for the Workspace the Budget is a part of.", + MarkdownDescription: "The token for the Workspace the Budget is a part of.", + }, + }, + CustomType: BudgetsType{ + ObjectType: types.ObjectType{ + AttrTypes: BudgetsValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + }, + }, + } +} + +type BudgetsModel struct { + Budgets types.List `tfsdk:"budgets"` +} + +var _ basetypes.ObjectTypable = BudgetsType{} + +type BudgetsType struct { + basetypes.ObjectType +} + +func (t BudgetsType) Equal(o attr.Type) bool { + other, ok := o.(BudgetsType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t BudgetsType) String() string { + return "BudgetsType" +} + +func (t BudgetsType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + budgetAlertTokensAttribute, ok := attributes["budget_alert_tokens"] + + if !ok { + diags.AddError( + "Attribute Missing", + `budget_alert_tokens is missing from object`) + + return nil, diags + } + + budgetAlertTokensVal, ok := budgetAlertTokensAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`budget_alert_tokens expected to be basetypes.ListValue, was: %T`, budgetAlertTokensAttribute)) + } + + costReportTokenAttribute, ok := attributes["cost_report_token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `cost_report_token is missing from object`) + + return nil, diags + } + + costReportTokenVal, ok := costReportTokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`cost_report_token expected to be basetypes.StringValue, was: %T`, costReportTokenAttribute)) + } + + createdAtAttribute, ok := attributes["created_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `created_at is missing from object`) + + return nil, diags + } + + createdAtVal, ok := createdAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`created_at expected to be basetypes.StringValue, was: %T`, createdAtAttribute)) + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return nil, diags + } + + nameVal, ok := nameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) + } + + performanceAttribute, ok := attributes["performance"] + + if !ok { + diags.AddError( + "Attribute Missing", + `performance is missing from object`) + + return nil, diags + } + + performanceVal, ok := performanceAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`performance expected to be basetypes.ListValue, was: %T`, performanceAttribute)) + } + + periodsAttribute, ok := attributes["periods"] + + if !ok { + diags.AddError( + "Attribute Missing", + `periods is missing from object`) + + return nil, diags + } + + periodsVal, ok := periodsAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`periods expected to be basetypes.ListValue, was: %T`, periodsAttribute)) + } + + tokenAttribute, ok := attributes["token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `token is missing from object`) + + return nil, diags + } + + tokenVal, ok := tokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`token expected to be basetypes.StringValue, was: %T`, tokenAttribute)) + } + + userTokenAttribute, ok := attributes["user_token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `user_token is missing from object`) + + return nil, diags + } + + userTokenVal, ok := userTokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`user_token expected to be basetypes.StringValue, was: %T`, userTokenAttribute)) + } + + workspaceTokenAttribute, ok := attributes["workspace_token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `workspace_token is missing from object`) + + return nil, diags + } + + workspaceTokenVal, ok := workspaceTokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`workspace_token expected to be basetypes.StringValue, was: %T`, workspaceTokenAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return BudgetsValue{ + BudgetAlertTokens: budgetAlertTokensVal, + CostReportToken: costReportTokenVal, + CreatedAt: createdAtVal, + Name: nameVal, + Performance: performanceVal, + Periods: periodsVal, + Token: tokenVal, + UserToken: userTokenVal, + WorkspaceToken: workspaceTokenVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewBudgetsValueNull() BudgetsValue { + return BudgetsValue{ + state: attr.ValueStateNull, + } +} + +func NewBudgetsValueUnknown() BudgetsValue { + return BudgetsValue{ + state: attr.ValueStateUnknown, + } +} + +func NewBudgetsValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (BudgetsValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing BudgetsValue Attribute Value", + "While creating a BudgetsValue value, a missing attribute value was detected. "+ + "A BudgetsValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("BudgetsValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid BudgetsValue Attribute Type", + "While creating a BudgetsValue value, an invalid attribute value was detected. "+ + "A BudgetsValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("BudgetsValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("BudgetsValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra BudgetsValue Attribute Value", + "While creating a BudgetsValue value, an extra attribute value was detected. "+ + "A BudgetsValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra BudgetsValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewBudgetsValueUnknown(), diags + } + + budgetAlertTokensAttribute, ok := attributes["budget_alert_tokens"] + + if !ok { + diags.AddError( + "Attribute Missing", + `budget_alert_tokens is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + budgetAlertTokensVal, ok := budgetAlertTokensAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`budget_alert_tokens expected to be basetypes.ListValue, was: %T`, budgetAlertTokensAttribute)) + } + + costReportTokenAttribute, ok := attributes["cost_report_token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `cost_report_token is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + costReportTokenVal, ok := costReportTokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`cost_report_token expected to be basetypes.StringValue, was: %T`, costReportTokenAttribute)) + } + + createdAtAttribute, ok := attributes["created_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `created_at is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + createdAtVal, ok := createdAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`created_at expected to be basetypes.StringValue, was: %T`, createdAtAttribute)) + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + nameVal, ok := nameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) + } + + performanceAttribute, ok := attributes["performance"] + + if !ok { + diags.AddError( + "Attribute Missing", + `performance is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + performanceVal, ok := performanceAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`performance expected to be basetypes.ListValue, was: %T`, performanceAttribute)) + } + + periodsAttribute, ok := attributes["periods"] + + if !ok { + diags.AddError( + "Attribute Missing", + `periods is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + periodsVal, ok := periodsAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`periods expected to be basetypes.ListValue, was: %T`, periodsAttribute)) + } + + tokenAttribute, ok := attributes["token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `token is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + tokenVal, ok := tokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`token expected to be basetypes.StringValue, was: %T`, tokenAttribute)) + } + + userTokenAttribute, ok := attributes["user_token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `user_token is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + userTokenVal, ok := userTokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`user_token expected to be basetypes.StringValue, was: %T`, userTokenAttribute)) + } + + workspaceTokenAttribute, ok := attributes["workspace_token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `workspace_token is missing from object`) + + return NewBudgetsValueUnknown(), diags + } + + workspaceTokenVal, ok := workspaceTokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`workspace_token expected to be basetypes.StringValue, was: %T`, workspaceTokenAttribute)) + } + + if diags.HasError() { + return NewBudgetsValueUnknown(), diags + } + + return BudgetsValue{ + BudgetAlertTokens: budgetAlertTokensVal, + CostReportToken: costReportTokenVal, + CreatedAt: createdAtVal, + Name: nameVal, + Performance: performanceVal, + Periods: periodsVal, + Token: tokenVal, + UserToken: userTokenVal, + WorkspaceToken: workspaceTokenVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewBudgetsValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) BudgetsValue { + object, diags := NewBudgetsValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewBudgetsValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t BudgetsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewBudgetsValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewBudgetsValueUnknown(), nil + } + + if in.IsNull() { + return NewBudgetsValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewBudgetsValueMust(BudgetsValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t BudgetsType) ValueType(ctx context.Context) attr.Value { + return BudgetsValue{} +} + +var _ basetypes.ObjectValuable = BudgetsValue{} + +type BudgetsValue struct { + BudgetAlertTokens basetypes.ListValue `tfsdk:"budget_alert_tokens"` + CostReportToken basetypes.StringValue `tfsdk:"cost_report_token"` + CreatedAt basetypes.StringValue `tfsdk:"created_at"` + Name basetypes.StringValue `tfsdk:"name"` + Performance basetypes.ListValue `tfsdk:"performance"` + Periods basetypes.ListValue `tfsdk:"periods"` + Token basetypes.StringValue `tfsdk:"token"` + UserToken basetypes.StringValue `tfsdk:"user_token"` + WorkspaceToken basetypes.StringValue `tfsdk:"workspace_token"` + state attr.ValueState +} + +func (v BudgetsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 9) + + var val tftypes.Value + var err error + + attrTypes["budget_alert_tokens"] = basetypes.ListType{ + ElemType: types.StringType, + }.TerraformType(ctx) + attrTypes["cost_report_token"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["created_at"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["name"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["performance"] = basetypes.ListType{ + ElemType: PerformanceValue{}.Type(ctx), + }.TerraformType(ctx) + attrTypes["periods"] = basetypes.ListType{ + ElemType: PeriodsValue{}.Type(ctx), + }.TerraformType(ctx) + attrTypes["token"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["user_token"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["workspace_token"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 9) + + val, err = v.BudgetAlertTokens.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["budget_alert_tokens"] = val + + val, err = v.CostReportToken.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["cost_report_token"] = val + + val, err = v.CreatedAt.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["created_at"] = val + + val, err = v.Name.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["name"] = val + + val, err = v.Performance.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["performance"] = val + + val, err = v.Periods.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["periods"] = val + + val, err = v.Token.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["token"] = val + + val, err = v.UserToken.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["user_token"] = val + + val, err = v.WorkspaceToken.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["workspace_token"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v BudgetsValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v BudgetsValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v BudgetsValue) String() string { + return "BudgetsValue" +} + +func (v BudgetsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + performance := types.ListValueMust( + PerformanceType{ + basetypes.ObjectType{ + AttrTypes: PerformanceValue{}.AttributeTypes(ctx), + }, + }, + v.Performance.Elements(), + ) + + if v.Performance.IsNull() { + performance = types.ListNull( + PerformanceType{ + basetypes.ObjectType{ + AttrTypes: PerformanceValue{}.AttributeTypes(ctx), + }, + }, + ) + } + + if v.Performance.IsUnknown() { + performance = types.ListUnknown( + PerformanceType{ + basetypes.ObjectType{ + AttrTypes: PerformanceValue{}.AttributeTypes(ctx), + }, + }, + ) + } + + periods := types.ListValueMust( + PeriodsType{ + basetypes.ObjectType{ + AttrTypes: PeriodsValue{}.AttributeTypes(ctx), + }, + }, + v.Periods.Elements(), + ) + + if v.Periods.IsNull() { + periods = types.ListNull( + PeriodsType{ + basetypes.ObjectType{ + AttrTypes: PeriodsValue{}.AttributeTypes(ctx), + }, + }, + ) + } + + if v.Periods.IsUnknown() { + periods = types.ListUnknown( + PeriodsType{ + basetypes.ObjectType{ + AttrTypes: PeriodsValue{}.AttributeTypes(ctx), + }, + }, + ) + } + + budgetAlertTokensVal, d := types.ListValue(types.StringType, v.BudgetAlertTokens.Elements()) + + diags.Append(d...) + + if d.HasError() { + return types.ObjectUnknown(map[string]attr.Type{ + "budget_alert_tokens": basetypes.ListType{ + ElemType: types.StringType, + }, + "cost_report_token": basetypes.StringType{}, + "created_at": basetypes.StringType{}, + "name": basetypes.StringType{}, + "performance": basetypes.ListType{ + ElemType: PerformanceValue{}.Type(ctx), + }, + "periods": basetypes.ListType{ + ElemType: PeriodsValue{}.Type(ctx), + }, + "token": basetypes.StringType{}, + "user_token": basetypes.StringType{}, + "workspace_token": basetypes.StringType{}, + }), diags + } + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "budget_alert_tokens": basetypes.ListType{ + ElemType: types.StringType, + }, + "cost_report_token": basetypes.StringType{}, + "created_at": basetypes.StringType{}, + "name": basetypes.StringType{}, + "performance": basetypes.ListType{ + ElemType: PerformanceValue{}.Type(ctx), + }, + "periods": basetypes.ListType{ + ElemType: PeriodsValue{}.Type(ctx), + }, + "token": basetypes.StringType{}, + "user_token": basetypes.StringType{}, + "workspace_token": basetypes.StringType{}, + }, + map[string]attr.Value{ + "budget_alert_tokens": budgetAlertTokensVal, + "cost_report_token": v.CostReportToken, + "created_at": v.CreatedAt, + "name": v.Name, + "performance": performance, + "periods": periods, + "token": v.Token, + "user_token": v.UserToken, + "workspace_token": v.WorkspaceToken, + }) + + return objVal, diags +} + +func (v BudgetsValue) Equal(o attr.Value) bool { + other, ok := o.(BudgetsValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.BudgetAlertTokens.Equal(other.BudgetAlertTokens) { + return false + } + + if !v.CostReportToken.Equal(other.CostReportToken) { + return false + } + + if !v.CreatedAt.Equal(other.CreatedAt) { + return false + } + + if !v.Name.Equal(other.Name) { + return false + } + + if !v.Performance.Equal(other.Performance) { + return false + } + + if !v.Periods.Equal(other.Periods) { + return false + } + + if !v.Token.Equal(other.Token) { + return false + } + + if !v.UserToken.Equal(other.UserToken) { + return false + } + + if !v.WorkspaceToken.Equal(other.WorkspaceToken) { + return false + } + + return true +} + +func (v BudgetsValue) Type(ctx context.Context) attr.Type { + return BudgetsType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v BudgetsValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "budget_alert_tokens": basetypes.ListType{ + ElemType: types.StringType, + }, + "cost_report_token": basetypes.StringType{}, + "created_at": basetypes.StringType{}, + "name": basetypes.StringType{}, + "performance": basetypes.ListType{ + ElemType: PerformanceValue{}.Type(ctx), + }, + "periods": basetypes.ListType{ + ElemType: PeriodsValue{}.Type(ctx), + }, + "token": basetypes.StringType{}, + "user_token": basetypes.StringType{}, + "workspace_token": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = PerformanceType{} + +type PerformanceType struct { + basetypes.ObjectType +} + +func (t PerformanceType) Equal(o attr.Type) bool { + other, ok := o.(PerformanceType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PerformanceType) String() string { + return "PerformanceType" +} + +func (t PerformanceType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + actualAttribute, ok := attributes["actual"] + + if !ok { + diags.AddError( + "Attribute Missing", + `actual is missing from object`) + + return nil, diags + } + + actualVal, ok := actualAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`actual expected to be basetypes.StringValue, was: %T`, actualAttribute)) + } + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return nil, diags + } + + amountVal, ok := amountAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + } + + dateAttribute, ok := attributes["date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `date is missing from object`) + + return nil, diags + } + + dateVal, ok := dateAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`date expected to be basetypes.StringValue, was: %T`, dateAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PerformanceValue{ + Actual: actualVal, + Amount: amountVal, + Date: dateVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPerformanceValueNull() PerformanceValue { + return PerformanceValue{ + state: attr.ValueStateNull, + } +} + +func NewPerformanceValueUnknown() PerformanceValue { + return PerformanceValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPerformanceValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PerformanceValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing PerformanceValue Attribute Value", + "While creating a PerformanceValue value, a missing attribute value was detected. "+ + "A PerformanceValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PerformanceValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PerformanceValue Attribute Type", + "While creating a PerformanceValue value, an invalid attribute value was detected. "+ + "A PerformanceValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PerformanceValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PerformanceValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PerformanceValue Attribute Value", + "While creating a PerformanceValue value, an extra attribute value was detected. "+ + "A PerformanceValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra PerformanceValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPerformanceValueUnknown(), diags + } + + actualAttribute, ok := attributes["actual"] + + if !ok { + diags.AddError( + "Attribute Missing", + `actual is missing from object`) + + return NewPerformanceValueUnknown(), diags + } + + actualVal, ok := actualAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`actual expected to be basetypes.StringValue, was: %T`, actualAttribute)) + } + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return NewPerformanceValueUnknown(), diags + } + + amountVal, ok := amountAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + } + + dateAttribute, ok := attributes["date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `date is missing from object`) + + return NewPerformanceValueUnknown(), diags + } + + dateVal, ok := dateAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`date expected to be basetypes.StringValue, was: %T`, dateAttribute)) + } + + if diags.HasError() { + return NewPerformanceValueUnknown(), diags + } + + return PerformanceValue{ + Actual: actualVal, + Amount: amountVal, + Date: dateVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPerformanceValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PerformanceValue { + object, diags := NewPerformanceValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewPerformanceValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PerformanceType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPerformanceValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewPerformanceValueUnknown(), nil + } + + if in.IsNull() { + return NewPerformanceValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewPerformanceValueMust(PerformanceValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PerformanceType) ValueType(ctx context.Context) attr.Value { + return PerformanceValue{} +} + +var _ basetypes.ObjectValuable = PerformanceValue{} + +type PerformanceValue struct { + Actual basetypes.StringValue `tfsdk:"actual"` + Amount basetypes.StringValue `tfsdk:"amount"` + Date basetypes.StringValue `tfsdk:"date"` + state attr.ValueState +} + +func (v PerformanceValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 3) + + var val tftypes.Value + var err error + + attrTypes["actual"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["amount"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["date"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 3) + + val, err = v.Actual.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["actual"] = val + + val, err = v.Amount.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["amount"] = val + + val, err = v.Date.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["date"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v PerformanceValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PerformanceValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PerformanceValue) String() string { + return "PerformanceValue" +} + +func (v PerformanceValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "actual": basetypes.StringType{}, + "amount": basetypes.StringType{}, + "date": basetypes.StringType{}, + }, + map[string]attr.Value{ + "actual": v.Actual, + "amount": v.Amount, + "date": v.Date, + }) + + return objVal, diags +} + +func (v PerformanceValue) Equal(o attr.Value) bool { + other, ok := o.(PerformanceValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Actual.Equal(other.Actual) { + return false + } + + if !v.Amount.Equal(other.Amount) { + return false + } + + if !v.Date.Equal(other.Date) { + return false + } + + return true +} + +func (v PerformanceValue) Type(ctx context.Context) attr.Type { + return PerformanceType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PerformanceValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "actual": basetypes.StringType{}, + "amount": basetypes.StringType{}, + "date": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = PeriodsType{} + +type PeriodsType struct { + basetypes.ObjectType +} + +func (t PeriodsType) Equal(o attr.Type) bool { + other, ok := o.(PeriodsType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PeriodsType) String() string { + return "PeriodsType" +} + +func (t PeriodsType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return nil, diags + } + + amountVal, ok := amountAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + } + + endAtAttribute, ok := attributes["end_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `end_at is missing from object`) + + return nil, diags + } + + endAtVal, ok := endAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`end_at expected to be basetypes.StringValue, was: %T`, endAtAttribute)) + } + + startAtAttribute, ok := attributes["start_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `start_at is missing from object`) + + return nil, diags + } + + startAtVal, ok := startAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`start_at expected to be basetypes.StringValue, was: %T`, startAtAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PeriodsValue{ + Amount: amountVal, + EndAt: endAtVal, + StartAt: startAtVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPeriodsValueNull() PeriodsValue { + return PeriodsValue{ + state: attr.ValueStateNull, + } +} + +func NewPeriodsValueUnknown() PeriodsValue { + return PeriodsValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPeriodsValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PeriodsValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing PeriodsValue Attribute Value", + "While creating a PeriodsValue value, a missing attribute value was detected. "+ + "A PeriodsValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PeriodsValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PeriodsValue Attribute Type", + "While creating a PeriodsValue value, an invalid attribute value was detected. "+ + "A PeriodsValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PeriodsValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PeriodsValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PeriodsValue Attribute Value", + "While creating a PeriodsValue value, an extra attribute value was detected. "+ + "A PeriodsValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra PeriodsValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPeriodsValueUnknown(), diags + } + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return NewPeriodsValueUnknown(), diags + } + + amountVal, ok := amountAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + } + + endAtAttribute, ok := attributes["end_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `end_at is missing from object`) + + return NewPeriodsValueUnknown(), diags + } + + endAtVal, ok := endAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`end_at expected to be basetypes.StringValue, was: %T`, endAtAttribute)) + } + + startAtAttribute, ok := attributes["start_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `start_at is missing from object`) + + return NewPeriodsValueUnknown(), diags + } + + startAtVal, ok := startAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`start_at expected to be basetypes.StringValue, was: %T`, startAtAttribute)) + } + + if diags.HasError() { + return NewPeriodsValueUnknown(), diags + } + + return PeriodsValue{ + Amount: amountVal, + EndAt: endAtVal, + StartAt: startAtVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPeriodsValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PeriodsValue { + object, diags := NewPeriodsValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewPeriodsValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PeriodsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPeriodsValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewPeriodsValueUnknown(), nil + } + + if in.IsNull() { + return NewPeriodsValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewPeriodsValueMust(PeriodsValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PeriodsType) ValueType(ctx context.Context) attr.Value { + return PeriodsValue{} +} + +var _ basetypes.ObjectValuable = PeriodsValue{} + +type PeriodsValue struct { + Amount basetypes.StringValue `tfsdk:"amount"` + EndAt basetypes.StringValue `tfsdk:"end_at"` + StartAt basetypes.StringValue `tfsdk:"start_at"` + state attr.ValueState +} + +func (v PeriodsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 3) + + var val tftypes.Value + var err error + + attrTypes["amount"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["end_at"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["start_at"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 3) + + val, err = v.Amount.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["amount"] = val + + val, err = v.EndAt.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["end_at"] = val + + val, err = v.StartAt.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["start_at"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v PeriodsValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PeriodsValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PeriodsValue) String() string { + return "PeriodsValue" +} + +func (v PeriodsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "amount": basetypes.StringType{}, + "end_at": basetypes.StringType{}, + "start_at": basetypes.StringType{}, + }, + map[string]attr.Value{ + "amount": v.Amount, + "end_at": v.EndAt, + "start_at": v.StartAt, + }) + + return objVal, diags +} + +func (v PeriodsValue) Equal(o attr.Value) bool { + other, ok := o.(PeriodsValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Amount.Equal(other.Amount) { + return false + } + + if !v.EndAt.Equal(other.EndAt) { + return false + } + + if !v.StartAt.Equal(other.StartAt) { + return false + } + + return true +} + +func (v PeriodsValue) Type(ctx context.Context) attr.Type { + return PeriodsType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PeriodsValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "amount": basetypes.StringType{}, + "end_at": basetypes.StringType{}, + "start_at": basetypes.StringType{}, + } +} diff --git a/vantage/resource_budget/budget_resource_gen.go b/vantage/resource_budget/budget_resource_gen.go new file mode 100644 index 0000000..f9078fa --- /dev/null +++ b/vantage/resource_budget/budget_resource_gen.go @@ -0,0 +1,1427 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package resource_budget + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func BudgetResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "budget_alert_tokens": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + Description: "The tokens of the BudgetAlerts associated with the Budget.", + MarkdownDescription: "The tokens of the BudgetAlerts associated with the Budget.", + }, + "cost_report_token": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The CostReport token.", + MarkdownDescription: "The CostReport token.", + }, + "created_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the Budget.", + MarkdownDescription: "The name of the Budget.", + }, + "performance": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "actual": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + "amount": schema.StringAttribute{ + Computed: true, + Description: "The amount of the Budget Period as a string to ensure precision.", + MarkdownDescription: "The amount of the Budget Period as a string to ensure precision.", + }, + "date": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + }, + CustomType: PerformanceType{ + ObjectType: types.ObjectType{ + AttrTypes: PerformanceValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + Description: "The historical performance of the Budget.", + MarkdownDescription: "The historical performance of the Budget.", + }, + "periods": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "amount": schema.StringAttribute{ + Computed: true, + Description: "The amount of the Budget Period as a string to ensure precision.", + MarkdownDescription: "The amount of the Budget Period as a string to ensure precision.", + }, + "end_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + "start_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + }, + CustomType: PeriodsType{ + ObjectType: types.ObjectType{ + AttrTypes: PeriodsValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + Description: "The budget periods associated with the Budget.", + MarkdownDescription: "The budget periods associated with the Budget.", + }, + "periods_attributes": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "amount": schema.Float64Attribute{ + Required: true, + }, + "end_at": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "start_at": schema.StringAttribute{ + Required: true, + }, + }, + CustomType: PeriodsAttributesType{ + ObjectType: types.ObjectType{ + AttrTypes: PeriodsAttributesValue{}.AttributeTypes(ctx), + }, + }, + }, + Optional: true, + Computed: true, + }, + "token": schema.StringAttribute{ + Computed: true, + Description: "The token of the budget", + MarkdownDescription: "The token of the budget", + }, + "user_token": schema.StringAttribute{ + Computed: true, + Description: "The token for the User who created this Budget.", + MarkdownDescription: "The token for the User who created this Budget.", + }, + "workspace_token": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The token of the Workspace to add the Budget to.", + MarkdownDescription: "The token of the Workspace to add the Budget to.", + }, + }, + } +} + +type BudgetModel struct { + BudgetAlertTokens types.List `tfsdk:"budget_alert_tokens"` + CostReportToken types.String `tfsdk:"cost_report_token"` + CreatedAt types.String `tfsdk:"created_at"` + Name types.String `tfsdk:"name"` + Performance types.List `tfsdk:"performance"` + Periods types.List `tfsdk:"periods"` + PeriodsAttributes types.List `tfsdk:"periods_attributes"` + Token types.String `tfsdk:"token"` + UserToken types.String `tfsdk:"user_token"` + WorkspaceToken types.String `tfsdk:"workspace_token"` +} + +var _ basetypes.ObjectTypable = PerformanceType{} + +type PerformanceType struct { + basetypes.ObjectType +} + +func (t PerformanceType) Equal(o attr.Type) bool { + other, ok := o.(PerformanceType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PerformanceType) String() string { + return "PerformanceType" +} + +func (t PerformanceType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + actualAttribute, ok := attributes["actual"] + + if !ok { + diags.AddError( + "Attribute Missing", + `actual is missing from object`) + + return nil, diags + } + + actualVal, ok := actualAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`actual expected to be basetypes.StringValue, was: %T`, actualAttribute)) + } + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return nil, diags + } + + amountVal, ok := amountAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + } + + dateAttribute, ok := attributes["date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `date is missing from object`) + + return nil, diags + } + + dateVal, ok := dateAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`date expected to be basetypes.StringValue, was: %T`, dateAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PerformanceValue{ + Actual: actualVal, + Amount: amountVal, + Date: dateVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPerformanceValueNull() PerformanceValue { + return PerformanceValue{ + state: attr.ValueStateNull, + } +} + +func NewPerformanceValueUnknown() PerformanceValue { + return PerformanceValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPerformanceValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PerformanceValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing PerformanceValue Attribute Value", + "While creating a PerformanceValue value, a missing attribute value was detected. "+ + "A PerformanceValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PerformanceValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PerformanceValue Attribute Type", + "While creating a PerformanceValue value, an invalid attribute value was detected. "+ + "A PerformanceValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PerformanceValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PerformanceValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PerformanceValue Attribute Value", + "While creating a PerformanceValue value, an extra attribute value was detected. "+ + "A PerformanceValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra PerformanceValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPerformanceValueUnknown(), diags + } + + actualAttribute, ok := attributes["actual"] + + if !ok { + diags.AddError( + "Attribute Missing", + `actual is missing from object`) + + return NewPerformanceValueUnknown(), diags + } + + actualVal, ok := actualAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`actual expected to be basetypes.StringValue, was: %T`, actualAttribute)) + } + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return NewPerformanceValueUnknown(), diags + } + + amountVal, ok := amountAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + } + + dateAttribute, ok := attributes["date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `date is missing from object`) + + return NewPerformanceValueUnknown(), diags + } + + dateVal, ok := dateAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`date expected to be basetypes.StringValue, was: %T`, dateAttribute)) + } + + if diags.HasError() { + return NewPerformanceValueUnknown(), diags + } + + return PerformanceValue{ + Actual: actualVal, + Amount: amountVal, + Date: dateVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPerformanceValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PerformanceValue { + object, diags := NewPerformanceValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewPerformanceValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PerformanceType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPerformanceValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewPerformanceValueUnknown(), nil + } + + if in.IsNull() { + return NewPerformanceValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewPerformanceValueMust(PerformanceValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PerformanceType) ValueType(ctx context.Context) attr.Value { + return PerformanceValue{} +} + +var _ basetypes.ObjectValuable = PerformanceValue{} + +type PerformanceValue struct { + Actual basetypes.StringValue `tfsdk:"actual"` + Amount basetypes.StringValue `tfsdk:"amount"` + Date basetypes.StringValue `tfsdk:"date"` + state attr.ValueState +} + +func (v PerformanceValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 3) + + var val tftypes.Value + var err error + + attrTypes["actual"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["amount"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["date"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 3) + + val, err = v.Actual.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["actual"] = val + + val, err = v.Amount.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["amount"] = val + + val, err = v.Date.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["date"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v PerformanceValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PerformanceValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PerformanceValue) String() string { + return "PerformanceValue" +} + +func (v PerformanceValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "actual": basetypes.StringType{}, + "amount": basetypes.StringType{}, + "date": basetypes.StringType{}, + }, + map[string]attr.Value{ + "actual": v.Actual, + "amount": v.Amount, + "date": v.Date, + }) + + return objVal, diags +} + +func (v PerformanceValue) Equal(o attr.Value) bool { + other, ok := o.(PerformanceValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Actual.Equal(other.Actual) { + return false + } + + if !v.Amount.Equal(other.Amount) { + return false + } + + if !v.Date.Equal(other.Date) { + return false + } + + return true +} + +func (v PerformanceValue) Type(ctx context.Context) attr.Type { + return PerformanceType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PerformanceValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "actual": basetypes.StringType{}, + "amount": basetypes.StringType{}, + "date": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = PeriodsType{} + +type PeriodsType struct { + basetypes.ObjectType +} + +func (t PeriodsType) Equal(o attr.Type) bool { + other, ok := o.(PeriodsType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PeriodsType) String() string { + return "PeriodsType" +} + +func (t PeriodsType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return nil, diags + } + + amountVal, ok := amountAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + } + + endAtAttribute, ok := attributes["end_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `end_at is missing from object`) + + return nil, diags + } + + endAtVal, ok := endAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`end_at expected to be basetypes.StringValue, was: %T`, endAtAttribute)) + } + + startAtAttribute, ok := attributes["start_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `start_at is missing from object`) + + return nil, diags + } + + startAtVal, ok := startAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`start_at expected to be basetypes.StringValue, was: %T`, startAtAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PeriodsValue{ + Amount: amountVal, + EndAt: endAtVal, + StartAt: startAtVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPeriodsValueNull() PeriodsValue { + return PeriodsValue{ + state: attr.ValueStateNull, + } +} + +func NewPeriodsValueUnknown() PeriodsValue { + return PeriodsValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPeriodsValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PeriodsValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing PeriodsValue Attribute Value", + "While creating a PeriodsValue value, a missing attribute value was detected. "+ + "A PeriodsValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PeriodsValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PeriodsValue Attribute Type", + "While creating a PeriodsValue value, an invalid attribute value was detected. "+ + "A PeriodsValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PeriodsValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PeriodsValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PeriodsValue Attribute Value", + "While creating a PeriodsValue value, an extra attribute value was detected. "+ + "A PeriodsValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra PeriodsValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPeriodsValueUnknown(), diags + } + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return NewPeriodsValueUnknown(), diags + } + + amountVal, ok := amountAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + } + + endAtAttribute, ok := attributes["end_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `end_at is missing from object`) + + return NewPeriodsValueUnknown(), diags + } + + endAtVal, ok := endAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`end_at expected to be basetypes.StringValue, was: %T`, endAtAttribute)) + } + + startAtAttribute, ok := attributes["start_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `start_at is missing from object`) + + return NewPeriodsValueUnknown(), diags + } + + startAtVal, ok := startAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`start_at expected to be basetypes.StringValue, was: %T`, startAtAttribute)) + } + + if diags.HasError() { + return NewPeriodsValueUnknown(), diags + } + + return PeriodsValue{ + Amount: amountVal, + EndAt: endAtVal, + StartAt: startAtVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPeriodsValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PeriodsValue { + object, diags := NewPeriodsValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewPeriodsValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PeriodsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPeriodsValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewPeriodsValueUnknown(), nil + } + + if in.IsNull() { + return NewPeriodsValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewPeriodsValueMust(PeriodsValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PeriodsType) ValueType(ctx context.Context) attr.Value { + return PeriodsValue{} +} + +var _ basetypes.ObjectValuable = PeriodsValue{} + +type PeriodsValue struct { + Amount basetypes.StringValue `tfsdk:"amount"` + EndAt basetypes.StringValue `tfsdk:"end_at"` + StartAt basetypes.StringValue `tfsdk:"start_at"` + state attr.ValueState +} + +func (v PeriodsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 3) + + var val tftypes.Value + var err error + + attrTypes["amount"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["end_at"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["start_at"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 3) + + val, err = v.Amount.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["amount"] = val + + val, err = v.EndAt.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["end_at"] = val + + val, err = v.StartAt.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["start_at"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v PeriodsValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PeriodsValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PeriodsValue) String() string { + return "PeriodsValue" +} + +func (v PeriodsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "amount": basetypes.StringType{}, + "end_at": basetypes.StringType{}, + "start_at": basetypes.StringType{}, + }, + map[string]attr.Value{ + "amount": v.Amount, + "end_at": v.EndAt, + "start_at": v.StartAt, + }) + + return objVal, diags +} + +func (v PeriodsValue) Equal(o attr.Value) bool { + other, ok := o.(PeriodsValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Amount.Equal(other.Amount) { + return false + } + + if !v.EndAt.Equal(other.EndAt) { + return false + } + + if !v.StartAt.Equal(other.StartAt) { + return false + } + + return true +} + +func (v PeriodsValue) Type(ctx context.Context) attr.Type { + return PeriodsType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PeriodsValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "amount": basetypes.StringType{}, + "end_at": basetypes.StringType{}, + "start_at": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = PeriodsAttributesType{} + +type PeriodsAttributesType struct { + basetypes.ObjectType +} + +func (t PeriodsAttributesType) Equal(o attr.Type) bool { + other, ok := o.(PeriodsAttributesType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PeriodsAttributesType) String() string { + return "PeriodsAttributesType" +} + +func (t PeriodsAttributesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return nil, diags + } + + amountVal, ok := amountAttribute.(basetypes.Float64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.Float64Value, was: %T`, amountAttribute)) + } + + endAtAttribute, ok := attributes["end_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `end_at is missing from object`) + + return nil, diags + } + + endAtVal, ok := endAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`end_at expected to be basetypes.StringValue, was: %T`, endAtAttribute)) + } + + startAtAttribute, ok := attributes["start_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `start_at is missing from object`) + + return nil, diags + } + + startAtVal, ok := startAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`start_at expected to be basetypes.StringValue, was: %T`, startAtAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PeriodsAttributesValue{ + Amount: amountVal, + EndAt: endAtVal, + StartAt: startAtVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPeriodsAttributesValueNull() PeriodsAttributesValue { + return PeriodsAttributesValue{ + state: attr.ValueStateNull, + } +} + +func NewPeriodsAttributesValueUnknown() PeriodsAttributesValue { + return PeriodsAttributesValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPeriodsAttributesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PeriodsAttributesValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing PeriodsAttributesValue Attribute Value", + "While creating a PeriodsAttributesValue value, a missing attribute value was detected. "+ + "A PeriodsAttributesValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PeriodsAttributesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PeriodsAttributesValue Attribute Type", + "While creating a PeriodsAttributesValue value, an invalid attribute value was detected. "+ + "A PeriodsAttributesValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("PeriodsAttributesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PeriodsAttributesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PeriodsAttributesValue Attribute Value", + "While creating a PeriodsAttributesValue value, an extra attribute value was detected. "+ + "A PeriodsAttributesValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra PeriodsAttributesValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPeriodsAttributesValueUnknown(), diags + } + + amountAttribute, ok := attributes["amount"] + + if !ok { + diags.AddError( + "Attribute Missing", + `amount is missing from object`) + + return NewPeriodsAttributesValueUnknown(), diags + } + + amountVal, ok := amountAttribute.(basetypes.Float64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`amount expected to be basetypes.Float64Value, was: %T`, amountAttribute)) + } + + endAtAttribute, ok := attributes["end_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `end_at is missing from object`) + + return NewPeriodsAttributesValueUnknown(), diags + } + + endAtVal, ok := endAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`end_at expected to be basetypes.StringValue, was: %T`, endAtAttribute)) + } + + startAtAttribute, ok := attributes["start_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `start_at is missing from object`) + + return NewPeriodsAttributesValueUnknown(), diags + } + + startAtVal, ok := startAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`start_at expected to be basetypes.StringValue, was: %T`, startAtAttribute)) + } + + if diags.HasError() { + return NewPeriodsAttributesValueUnknown(), diags + } + + return PeriodsAttributesValue{ + Amount: amountVal, + EndAt: endAtVal, + StartAt: startAtVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPeriodsAttributesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PeriodsAttributesValue { + object, diags := NewPeriodsAttributesValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewPeriodsAttributesValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PeriodsAttributesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPeriodsAttributesValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewPeriodsAttributesValueUnknown(), nil + } + + if in.IsNull() { + return NewPeriodsAttributesValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewPeriodsAttributesValueMust(PeriodsAttributesValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PeriodsAttributesType) ValueType(ctx context.Context) attr.Value { + return PeriodsAttributesValue{} +} + +var _ basetypes.ObjectValuable = PeriodsAttributesValue{} + +type PeriodsAttributesValue struct { + Amount basetypes.Float64Value `tfsdk:"amount"` + EndAt basetypes.StringValue `tfsdk:"end_at"` + StartAt basetypes.StringValue `tfsdk:"start_at"` + state attr.ValueState +} + +func (v PeriodsAttributesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 3) + + var val tftypes.Value + var err error + + attrTypes["amount"] = basetypes.Float64Type{}.TerraformType(ctx) + attrTypes["end_at"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["start_at"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 3) + + val, err = v.Amount.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["amount"] = val + + val, err = v.EndAt.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["end_at"] = val + + val, err = v.StartAt.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["start_at"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v PeriodsAttributesValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PeriodsAttributesValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PeriodsAttributesValue) String() string { + return "PeriodsAttributesValue" +} + +func (v PeriodsAttributesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "amount": basetypes.Float64Type{}, + "end_at": basetypes.StringType{}, + "start_at": basetypes.StringType{}, + }, + map[string]attr.Value{ + "amount": v.Amount, + "end_at": v.EndAt, + "start_at": v.StartAt, + }) + + return objVal, diags +} + +func (v PeriodsAttributesValue) Equal(o attr.Value) bool { + other, ok := o.(PeriodsAttributesValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Amount.Equal(other.Amount) { + return false + } + + if !v.EndAt.Equal(other.EndAt) { + return false + } + + if !v.StartAt.Equal(other.StartAt) { + return false + } + + return true +} + +func (v PeriodsAttributesValue) Type(ctx context.Context) attr.Type { + return PeriodsAttributesType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PeriodsAttributesValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "amount": basetypes.Float64Type{}, + "end_at": basetypes.StringType{}, + "start_at": basetypes.StringType{}, + } +} From 770308eb0dc70784680af9bd654c5dea6076b2c8 Mon Sep 17 00:00:00 2001 From: Andy Cheung Date: Tue, 16 Apr 2024 13:21:01 -0400 Subject: [PATCH 2/2] budget resource. needs tests. bump vantage-go examples tf fmt cleanup --- docs/data-sources/budgets.md | 56 ++ docs/resources/budget.md | 58 +++ examples/cost_report/main.tf | 39 +- examples/data_sources/budgets.tf | 5 + examples/resources/vantage_budget/resource.tf | 11 + go.mod | 6 +- go.sum | 4 +- vantage/budget_resource.go | 263 ++++++++++ vantage/budget_resource_model.go | 194 +++++-- vantage/budgets_data_source.go | 2 +- vantage/provider.go | 2 + .../resource_budget/budget_resource_gen.go | 490 +----------------- 12 files changed, 615 insertions(+), 515 deletions(-) create mode 100644 docs/data-sources/budgets.md create mode 100644 docs/resources/budget.md create mode 100644 examples/data_sources/budgets.tf create mode 100644 examples/resources/vantage_budget/resource.tf create mode 100644 vantage/budget_resource.go diff --git a/docs/data-sources/budgets.md b/docs/data-sources/budgets.md new file mode 100644 index 0000000..59d23e6 --- /dev/null +++ b/docs/data-sources/budgets.md @@ -0,0 +1,56 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vantage_budgets Data Source - terraform-provider-vantage" +subcategory: "" +description: |- + +--- + +# vantage_budgets (Data Source) + + + + + + +## Schema + +### Read-Only + +- `budgets` (Attributes List) (see [below for nested schema](#nestedatt--budgets)) + + +### Nested Schema for `budgets` + +Read-Only: + +- `budget_alert_tokens` (List of String) The tokens of the BudgetAlerts associated with the Budget. +- `cost_report_token` (String) The token of the Report associated with the Budget. +- `created_at` (String) The date and time, in UTC, the Budget was created. ISO 8601 Formatted. +- `name` (String) The name of the Budget. +- `performance` (Attributes List) The historical performance of the Budget. (see [below for nested schema](#nestedatt--budgets--performance)) +- `periods` (Attributes List) The budget periods associated with the Budget. (see [below for nested schema](#nestedatt--budgets--periods)) +- `token` (String) +- `user_token` (String) The token for the User who created this Budget. +- `workspace_token` (String) The token for the Workspace the Budget is a part of. + + +### Nested Schema for `budgets.performance` + +Read-Only: + +- `actual` (String) The date and time, in UTC, the Budget was created. ISO 8601 Formatted. +- `amount` (String) The amount of the Budget Period as a string to ensure precision. +- `date` (String) The date and time, in UTC, the Budget was created. ISO 8601 Formatted. + + + +### Nested Schema for `budgets.periods` + +Read-Only: + +- `amount` (String) The amount of the Budget Period as a string to ensure precision. +- `end_at` (String) The date and time, in UTC, the Budget was created. ISO 8601 Formatted. +- `start_at` (String) The date and time, in UTC, the Budget was created. ISO 8601 Formatted. + + diff --git a/docs/resources/budget.md b/docs/resources/budget.md new file mode 100644 index 0000000..0938697 --- /dev/null +++ b/docs/resources/budget.md @@ -0,0 +1,58 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vantage_budget Resource - terraform-provider-vantage" +subcategory: "" +description: |- + +--- + +# vantage_budget (Resource) + + + + + + +## Schema + +### Required + +- `name` (String) The name of the Budget. + +### Optional + +- `cost_report_token` (String) The CostReport token. +- `periods` (Attributes List) The periods for the Budget. The start_at and end_at must be iso8601 formatted e.g. YYYY-MM-DD. (see [below for nested schema](#nestedatt--periods)) +- `workspace_token` (String) The token of the Workspace to add the Budget to. + +### Read-Only + +- `budget_alert_tokens` (List of String) The tokens of the BudgetAlerts associated with the Budget. +- `created_at` (String) The date and time, in UTC, the Budget was created. ISO 8601 Formatted. +- `performance` (Attributes List) The historical performance of the Budget. (see [below for nested schema](#nestedatt--performance)) +- `token` (String) The token of the budget +- `user_token` (String) The token for the User who created this Budget. + + +### Nested Schema for `periods` + +Required: + +- `amount` (Number) The amount of the period. +- `start_at` (String) The start date of the period. + +Optional: + +- `end_at` (String) The end date of the period. + + + +### Nested Schema for `performance` + +Read-Only: + +- `actual` (String) The date and time, in UTC, the Budget was created. ISO 8601 Formatted. +- `amount` (String) The amount of the Budget Period as a string to ensure precision. +- `date` (String) The date and time, in UTC, the Budget was created. ISO 8601 Formatted. + + diff --git a/examples/cost_report/main.tf b/examples/cost_report/main.tf index b39eb8c..130739b 100644 --- a/examples/cost_report/main.tf +++ b/examples/cost_report/main.tf @@ -27,7 +27,7 @@ resource "vantage_cost_report" "demo_report" { filter = "costs.provider = 'kubernetes'" saved_filter_tokens = [vantage_saved_filter.demo_filter.token] title = "Demo Report" - } +} resource "vantage_dashboard" "demo_dashboard" { widget_tokens = [vantage_cost_report.demo_report.token] title = "Demo Dashboard" @@ -54,27 +54,27 @@ resource "vantage_access_grant" "demo_access_grant" { } locals { - metrics_csv = csvdecode(file("${path.module}/metrics.csv")) + metrics_csv = csvdecode(file("${path.module}/metrics.csv")) sorted_dates = distinct(reverse(sort(local.metrics_csv[*].date))) sorted_metrics = flatten( - [for value in local.sorted_dates: - [ for elem in local.metrics_csv: - elem if value == elem.date - ] - ]) + [for value in local.sorted_dates : + [for elem in local.metrics_csv : + elem if value == elem.date + ] + ]) } resource "vantage_business_metric" "demo_metric2" { - title = "Demo Metric" + title = "Demo Metric" cost_report_tokens_with_metadata = [ { cost_report_token = vantage_cost_report.demo_report.token - unit_scale = "per_hundred" + unit_scale = "per_hundred" } ] values = [for row in local.sorted_metrics : { - date = row.date + date = row.date amount = row.amount }] } @@ -84,6 +84,23 @@ data "vantage_business_metrics" "demo2" {} output "business_metrics" { value = data.vantage_business_metrics.demo2 } - + +resource "vantage_budget" "demo_budget" { + name = "Demo Budget" + cost_report_token = vantage_cost_report.demo_report.token + periods = [ + { + start_at = "2023-12-01" + end_at = "2024-01-01" + amount = 1000 + } + ] +} + +data "vantage_budgets" "demo" {} + +output "budgets" { + value = data.vantage_budgets.demo +} \ No newline at end of file diff --git a/examples/data_sources/budgets.tf b/examples/data_sources/budgets.tf new file mode 100644 index 0000000..b53c3a6 --- /dev/null +++ b/examples/data_sources/budgets.tf @@ -0,0 +1,5 @@ +data "vantage_budgets" "all" {} + +output "all_budgets" { + value = data.vantage_budgets.all +} \ No newline at end of file diff --git a/examples/resources/vantage_budget/resource.tf b/examples/resources/vantage_budget/resource.tf new file mode 100644 index 0000000..799b06f --- /dev/null +++ b/examples/resources/vantage_budget/resource.tf @@ -0,0 +1,11 @@ +resource "vantage_budget" "demo_budget" { + name = "Demo Budget" + cost_report_token = vantage_cost_report.demo_report.token + periods = [ + { + start_at = "2023-12-01" + end_at = "2024-01-01" + amount = 1000 + } + ] +} diff --git a/go.mod b/go.mod index 1419b6f..8632110 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,9 @@ require ( github.com/hashicorp/terraform-plugin-docs v0.14.1 github.com/hashicorp/terraform-plugin-framework v1.5.0 github.com/hashicorp/terraform-plugin-testing v1.6.0 - github.com/vantage-sh/vantage-go v0.0.19 + github.com/vantage-sh/vantage-go v0.0.21 ) -replace github.com/vantage-sh/vantage-go v0.0.19 => ../vantage-go - require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect @@ -56,7 +54,7 @@ require ( github.com/hashicorp/terraform-json v0.18.0 // indirect github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 github.com/hashicorp/terraform-plugin-go v0.20.0 - github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect + github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect diff --git a/go.sum b/go.sum index 9f1c63b..97b7228 100644 --- a/go.sum +++ b/go.sum @@ -318,8 +318,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/vantage-sh/vantage-go v0.0.19 h1:4XrUBY80pjjlb24KVxBa0VfESowLNONzgdHdBA84wWA= -github.com/vantage-sh/vantage-go v0.0.19/go.mod h1:MsSI4gX/Wvkzo9kqWphZfE6puolmnbHcXSaoGkDCmXg= +github.com/vantage-sh/vantage-go v0.0.21 h1:jnJn5voUjQIyIZyE7Rpo7D4zcTBgrsgOWSkW+QrZMFg= +github.com/vantage-sh/vantage-go v0.0.21/go.mod h1:MsSI4gX/Wvkzo9kqWphZfE6puolmnbHcXSaoGkDCmXg= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= diff --git a/vantage/budget_resource.go b/vantage/budget_resource.go new file mode 100644 index 0000000..8a08116 --- /dev/null +++ b/vantage/budget_resource.go @@ -0,0 +1,263 @@ +package vantage + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/vantage-sh/terraform-provider-vantage/vantage/resource_budget" + budgetsv2 "github.com/vantage-sh/vantage-go/vantagev2/vantage/budgets" +) + +var _ resource.Resource = (*budgetResource)(nil) +var _ resource.ResourceWithConfigure = (*budgetResource)(nil) + +func NewBudgetResource() resource.Resource { + return &budgetResource{} +} + +type budgetResource struct { + client *Client +} + +func (r *budgetResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + r.client = req.ProviderData.(*Client) +} +func (r *budgetResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_budget" +} + +func (r *budgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "budget_alert_tokens": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + Description: "The tokens of the BudgetAlerts associated with the Budget.", + MarkdownDescription: "The tokens of the BudgetAlerts associated with the Budget.", + }, + "cost_report_token": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The CostReport token.", + MarkdownDescription: "The CostReport token.", + }, + "created_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "The name of the Budget.", + MarkdownDescription: "The name of the Budget.", + }, + "performance": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "actual": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + "amount": schema.StringAttribute{ + Computed: true, + Description: "The amount of the Budget Period as a string to ensure precision.", + MarkdownDescription: "The amount of the Budget Period as a string to ensure precision.", + }, + "date": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + }, + }, + CustomType: resource_budget.PerformanceType{ + ObjectType: types.ObjectType{ + AttrTypes: resource_budget.PerformanceValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + Description: "The historical performance of the Budget.", + MarkdownDescription: "The historical performance of the Budget.", + }, + "periods": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "amount": schema.Float64Attribute{ + Required: true, + Description: "The amount of the period.", + MarkdownDescription: "The amount of the period.", + }, + "end_at": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The end date of the period.", + MarkdownDescription: "The end date of the period.", + }, + "start_at": schema.StringAttribute{ + Required: true, + Description: "The start date of the period.", + MarkdownDescription: "The start date of the period.", + }, + }, + CustomType: resource_budget.PeriodsType{ + ObjectType: types.ObjectType{ + AttrTypes: resource_budget.PeriodsValue{}.AttributeTypes(ctx), + }, + }, + }, + Optional: true, + Computed: true, + Description: "The periods for the Budget. The start_at and end_at must be iso8601 formatted e.g. YYYY-MM-DD.", + MarkdownDescription: "The periods for the Budget. The start_at and end_at must be iso8601 formatted e.g. YYYY-MM-DD.", + }, + "token": schema.StringAttribute{ + Computed: true, + Description: "The token of the budget", + MarkdownDescription: "The token of the budget", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "user_token": schema.StringAttribute{ + Computed: true, + Description: "The token for the User who created this Budget.", + MarkdownDescription: "The token for the User who created this Budget.", + }, + "workspace_token": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The token of the Workspace to add the Budget to.", + MarkdownDescription: "The token of the Workspace to add the Budget to.", + }, + }, + } + +} + +func (r *budgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data budgetModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := budgetsv2.NewCreateBudgetParams().WithCreateBudget(toCreateModel(ctx, &resp.Diagnostics, data)) + out, err := r.client.V2.Budgets.CreateBudget(params, r.client.Auth) + + if err != nil { + if e, ok := err.(*budgetsv2.CreateBudgetBadRequest); ok { + handleBadRequest("Create Budget", &resp.Diagnostics, e.GetPayload()) + return + } + handleError("Create Budget", &resp.Diagnostics, err) + return + } + + tflog.Debug(ctx, "applyBudgetPayload create") + diag := applyBudgetPayload(ctx, false, out.Payload, &data) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *budgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data budgetModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := budgetsv2.NewGetBudgetParams().WithBudgetToken(data.Token.ValueString()) + out, err := r.client.V2.Budgets.GetBudget(params, r.client.Auth) + if err != nil { + if _, ok := err.(*budgetsv2.GetBudgetNotFound); ok { + resp.State.RemoveResource(ctx) + return + } + handleError("Get Budget", &resp.Diagnostics, err) + return + } + tflog.Debug(ctx, "applyBudgetPayload read") + diag := applyBudgetPayload(ctx, false, out.Payload, &data) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *budgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data budgetModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := budgetsv2.NewUpdateBudgetParams().WithUpdateBudget(toUpdateModel(ctx, &resp.Diagnostics, data)).WithBudgetToken(data.Token.ValueString()) + out, err := r.client.V2.Budgets.UpdateBudget(params, r.client.Auth) + + if err != nil { + if e, ok := err.(*budgetsv2.UpdateBudgetBadRequest); ok { + handleBadRequest("Update Budget", &resp.Diagnostics, e.GetPayload()) + return + } + handleError("Update Budget", &resp.Diagnostics, err) + return + } + tflog.Debug(ctx, "applyBudgetPayload update") + diag := applyBudgetPayload(ctx, false, out.Payload, &data) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *budgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data budgetModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := budgetsv2.NewDeleteBudgetParams().WithBudgetToken(data.Token.ValueString()) + _, err := r.client.V2.Budgets.DeleteBudget(params, r.client.Auth) + if err != nil { + if e, ok := err.(*budgetsv2.DeleteBudgetNotFound); ok { + handleBadRequest("Delete Budget", &resp.Diagnostics, e.GetPayload()) + return + } + handleError("Delete Budget", &resp.Diagnostics, err) + return + } + +} diff --git a/vantage/budget_resource_model.go b/vantage/budget_resource_model.go index 945ffcc..886cd15 100644 --- a/vantage/budget_resource_model.go +++ b/vantage/budget_resource_model.go @@ -2,32 +2,135 @@ package vantage import ( "context" + "fmt" + "strconv" + "strings" + "time" + "github.com/go-openapi/strfmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" resource_budget "github.com/vantage-sh/terraform-provider-vantage/vantage/resource_budget" modelsv2 "github.com/vantage-sh/vantage-go/vantagev2/models" ) -// let the budget model defined in the resource be the common model for +// let the budget, budget performance and budget period models +// defined in the resource_budget package be the common models for // both the data source and the resource type budgetModel resource_budget.BudgetModel - -type budgetPerformanceModel struct { - Actual types.String `tfsdk:"actual"` - Amount types.String `tfsdk:"amount"` - Date types.String `tfsdk:"budget"` +type budgetPerformanceModel resource_budget.PerformanceValue +type budgetPeriodResourceModel struct { + Amount types.Float64 `tfsdk:"amount"` + EndAt types.String `tfsdk:"end_at"` + StartAt types.String `tfsdk:"start_at"` } -type budgetPeriodModel struct { +type budgetPeriodDataSourceModel struct { Amount types.String `tfsdk:"amount"` EndAt types.String `tfsdk:"end_at"` StartAt types.String `tfsdk:"start_at"` } -// type budgetResourceModel struct {} -func applyBudgetPayload(ctx context.Context, src *modelsv2.Budget, dst *budgetModel) diag.Diagnostics { +// toCreateModel and toUpdateModel can be further refactored. +func toCreateModel(ctx context.Context, diags *diag.Diagnostics, src budgetModel) *modelsv2.CreateBudget { + dst := &modelsv2.CreateBudget{ + Name: src.Name.ValueStringPointer(), + CostReportToken: src.CostReportToken.ValueString(), + WorkspaceToken: src.WorkspaceToken.ValueString(), + } + + if !src.Periods.IsNull() && !src.Periods.IsUnknown() { + periods := make([]*budgetPeriodResourceModel, 0, len(src.Periods.Elements())) + if diag := src.Periods.ElementsAs(ctx, &periods, false); diag.HasError() { + diags.Append(diag...) + return nil + } + + dstValues := make([]*modelsv2.CreateBudgetPeriodsItems0, 0, len(periods)) + for _, p := range periods { + periodItem := &modelsv2.CreateBudgetPeriodsItems0{ + Amount: p.Amount.ValueFloat64Pointer(), + } + + if p.EndAt.String() != "" { + endAt, err := time.Parse("2006-01-02", strings.ReplaceAll(p.EndAt.String(), "\"", "")) + if err != nil { + diags.AddError("parsing error", fmt.Sprintf("failed to parse end_at: %s", err)) + return nil + } + + ea := strfmt.Date(endAt) + periodItem.EndAt = &ea + } + + startAt, err := time.Parse("2006-01-02", strings.ReplaceAll(p.StartAt.String(), "\"", "")) + if err != nil { + diags.AddError("parsing error", fmt.Sprintf("failed to parse start_at: %s", err)) + return nil + } + + if !startAt.IsZero() { + sa := strfmt.Date(startAt) + periodItem.StartAt = &sa + } + dstValues = append(dstValues, periodItem) + } + dst.Periods = dstValues + } + return dst +} + +func toUpdateModel(ctx context.Context, diags *diag.Diagnostics, src budgetModel) *modelsv2.UpdateBudget { + dst := &modelsv2.UpdateBudget{ + Name: src.Name.ValueString(), + CostReportToken: src.CostReportToken.ValueString(), + } + + if !src.Periods.IsNull() && !src.Periods.IsUnknown() { + periods := make([]*budgetPeriodResourceModel, 0, len(src.Periods.Elements())) + if diag := src.Periods.ElementsAs(ctx, &periods, false); diag.HasError() { + diags.Append(diag...) + return nil + } + + dstValues := make([]*modelsv2.UpdateBudgetPeriodsItems0, 0, len(periods)) + for _, p := range periods { + periodItem := &modelsv2.UpdateBudgetPeriodsItems0{ + Amount: p.Amount.ValueFloat64Pointer(), + } + + if p.EndAt.String() != "" { + endAt, err := time.Parse("2006-01-02", strings.ReplaceAll(p.EndAt.String(), "\"", "")) + if err != nil { + diags.AddError("parsing error", fmt.Sprintf("failed to parse end_at: %s", err)) + return nil + } + + ea := strfmt.Date(endAt) + periodItem.EndAt = &ea + } + + startAt, err := time.Parse("2006-01-02", strings.ReplaceAll(p.StartAt.String(), "\"", "")) + if err != nil { + diags.AddError("parsing error", fmt.Sprintf("failed to parse start_at: %s", err)) + return nil + } + + if !startAt.IsZero() { + sa := strfmt.Date(startAt) + periodItem.StartAt = &sa + } + dstValues = append(dstValues, periodItem) + } + dst.Periods = dstValues + } + + return dst +} + +func applyBudgetPayload(ctx context.Context, isDataSource bool, src *modelsv2.Budget, dst *budgetModel) diag.Diagnostics { dst.Token = types.StringValue(src.Token) dst.CreatedAt = types.StringValue(src.CreatedAt) dst.Name = types.StringValue(src.Name) @@ -70,29 +173,62 @@ func applyBudgetPayload(ctx context.Context, src *modelsv2.Budget, dst *budgetMo } if src.Periods != nil { - periods := make([]budgetPeriodModel, 0, len(src.Periods)) - for _, p := range src.Periods { - period := budgetPeriodModel{ - Amount: types.StringValue(p.Amount), - EndAt: types.StringValue(p.EndAt), - StartAt: types.StringValue(p.StartAt), + if isDataSource { + periods := make([]budgetPeriodDataSourceModel, 0, len(src.Periods)) + for _, p := range src.Periods { + period := budgetPeriodDataSourceModel{ + Amount: types.StringValue(p.Amount), + EndAt: types.StringValue(p.EndAt), + StartAt: types.StringValue(p.StartAt), + } + periods = append(periods, period) } - periods = append(periods, period) - } - l, d := types.ListValueFrom( - ctx, - types.ObjectType{AttrTypes: map[string]attr.Type{ - "amount": types.StringType, - "end": types.StringType, - "start": types.StringType, - }}, - periods, - ) - if d.HasError() { - return d + attrTypes := map[string]attr.Type{ + "amount": types.StringType, + "end_at": types.StringType, + "start_at": types.StringType, + } + + l, d := types.ListValueFrom( + ctx, + types.ObjectType{AttrTypes: attrTypes}, + periods, + ) + if d.HasError() { + return d + } + dst.Periods = l + } else { + periods := make([]budgetPeriodResourceModel, 0, len(src.Periods)) + for _, p := range src.Periods { + amt, _ := strconv.ParseFloat(p.Amount, 64) + + period := budgetPeriodResourceModel{ + Amount: types.Float64Value(amt), + EndAt: types.StringValue(p.EndAt), + StartAt: types.StringValue(p.StartAt), + } + periods = append(periods, period) + } + + attrTypes := map[string]attr.Type{ + "amount": types.Float64Type, + "end_at": types.StringType, + "start_at": types.StringType, + } + tflog.Debug(ctx, fmt.Sprintf("andy 2 isDataSource: %v, periods %v, attrTypes %v", isDataSource, periods, attrTypes)) + l, d := types.ListValueFrom( + ctx, + types.ObjectType{AttrTypes: attrTypes}, + periods, + ) + if d.HasError() { + return d + } + dst.Periods = l } - dst.Periods = l + } return nil } diff --git a/vantage/budgets_data_source.go b/vantage/budgets_data_source.go index 01e348e..228a030 100644 --- a/vantage/budgets_data_source.go +++ b/vantage/budgets_data_source.go @@ -59,7 +59,7 @@ func (d *budgetsDataSource) Read(ctx context.Context, req datasource.ReadRequest budgets := []budgetModel{} for _, budget := range out.Payload.Budgets { model := budgetModel{} - diag := applyBudgetPayload(ctx, budget, &model) + diag := applyBudgetPayload(ctx, true, budget, &model) if diag.HasError() { resp.Diagnostics.Append(diag...) return diff --git a/vantage/provider.go b/vantage/provider.go index ada55d3..b5e3eb2 100644 --- a/vantage/provider.go +++ b/vantage/provider.go @@ -156,6 +156,7 @@ func (p *vantageProvider) DataSources(_ context.Context) []func() datasource.Dat NewAnomalyNotificationsDataSource, NewVirtualTagConfigsDataSource, NewBusinessMetricsDataSource, + NewBudgetsDataSource, } } @@ -174,5 +175,6 @@ func (p *vantageProvider) Resources(_ context.Context) []func() resource.Resourc NewAnomalyNotificationResource, NewVirtualTagConfigResource, NewBusinessMetricResource, + NewBudgetResource, } } diff --git a/vantage/resource_budget/budget_resource_gen.go b/vantage/resource_budget/budget_resource_gen.go index f9078fa..3e3f072 100644 --- a/vantage/resource_budget/budget_resource_gen.go +++ b/vantage/resource_budget/budget_resource_gen.go @@ -72,20 +72,21 @@ func BudgetResourceSchema(ctx context.Context) schema.Schema { "periods": schema.ListNestedAttribute{ NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ - "amount": schema.StringAttribute{ - Computed: true, - Description: "The amount of the Budget Period as a string to ensure precision.", - MarkdownDescription: "The amount of the Budget Period as a string to ensure precision.", + "amount": schema.Float64Attribute{ + Required: true, + Description: "The amount of the period.", + MarkdownDescription: "The amount of the period.", }, "end_at": schema.StringAttribute{ + Optional: true, Computed: true, - Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", - MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + Description: "The end date of the period.", + MarkdownDescription: "The end date of the period.", }, "start_at": schema.StringAttribute{ - Computed: true, - Description: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", - MarkdownDescription: "The date and time, in UTC, the Budget was created. ISO 8601 Formatted.", + Required: true, + Description: "The start date of the period.", + MarkdownDescription: "The start date of the period.", }, }, CustomType: PeriodsType{ @@ -94,32 +95,10 @@ func BudgetResourceSchema(ctx context.Context) schema.Schema { }, }, }, + Optional: true, Computed: true, - Description: "The budget periods associated with the Budget.", - MarkdownDescription: "The budget periods associated with the Budget.", - }, - "periods_attributes": schema.ListNestedAttribute{ - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "amount": schema.Float64Attribute{ - Required: true, - }, - "end_at": schema.StringAttribute{ - Optional: true, - Computed: true, - }, - "start_at": schema.StringAttribute{ - Required: true, - }, - }, - CustomType: PeriodsAttributesType{ - ObjectType: types.ObjectType{ - AttrTypes: PeriodsAttributesValue{}.AttributeTypes(ctx), - }, - }, - }, - Optional: true, - Computed: true, + Description: "The periods for the Budget. The start_at and end_at must be iso8601 formatted e.g. YYYY-MM-DD.", + MarkdownDescription: "The periods for the Budget. The start_at and end_at must be iso8601 formatted e.g. YYYY-MM-DD.", }, "token": schema.StringAttribute{ Computed: true, @@ -148,7 +127,6 @@ type BudgetModel struct { Name types.String `tfsdk:"name"` Performance types.List `tfsdk:"performance"` Periods types.List `tfsdk:"periods"` - PeriodsAttributes types.List `tfsdk:"periods_attributes"` Token types.String `tfsdk:"token"` UserToken types.String `tfsdk:"user_token"` WorkspaceToken types.String `tfsdk:"workspace_token"` @@ -613,12 +591,12 @@ func (t PeriodsType) ValueFromObject(ctx context.Context, in basetypes.ObjectVal return nil, diags } - amountVal, ok := amountAttribute.(basetypes.StringValue) + amountVal, ok := amountAttribute.(basetypes.Float64Value) if !ok { diags.AddError( "Attribute Wrong Type", - fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + fmt.Sprintf(`amount expected to be basetypes.Float64Value, was: %T`, amountAttribute)) } endAtAttribute, ok := attributes["end_at"] @@ -742,12 +720,12 @@ func NewPeriodsValue(attributeTypes map[string]attr.Type, attributes map[string] return NewPeriodsValueUnknown(), diags } - amountVal, ok := amountAttribute.(basetypes.StringValue) + amountVal, ok := amountAttribute.(basetypes.Float64Value) if !ok { diags.AddError( "Attribute Wrong Type", - fmt.Sprintf(`amount expected to be basetypes.StringValue, was: %T`, amountAttribute)) + fmt.Sprintf(`amount expected to be basetypes.Float64Value, was: %T`, amountAttribute)) } endAtAttribute, ok := attributes["end_at"] @@ -866,9 +844,9 @@ func (t PeriodsType) ValueType(ctx context.Context) attr.Value { var _ basetypes.ObjectValuable = PeriodsValue{} type PeriodsValue struct { - Amount basetypes.StringValue `tfsdk:"amount"` - EndAt basetypes.StringValue `tfsdk:"end_at"` - StartAt basetypes.StringValue `tfsdk:"start_at"` + Amount basetypes.Float64Value `tfsdk:"amount"` + EndAt basetypes.StringValue `tfsdk:"end_at"` + StartAt basetypes.StringValue `tfsdk:"start_at"` state attr.ValueState } @@ -878,7 +856,7 @@ func (v PeriodsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, erro var val tftypes.Value var err error - attrTypes["amount"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["amount"] = basetypes.Float64Type{}.TerraformType(ctx) attrTypes["end_at"] = basetypes.StringType{}.TerraformType(ctx) attrTypes["start_at"] = basetypes.StringType{}.TerraformType(ctx) @@ -943,7 +921,7 @@ func (v PeriodsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, objVal, diags := types.ObjectValue( map[string]attr.Type{ - "amount": basetypes.StringType{}, + "amount": basetypes.Float64Type{}, "end_at": basetypes.StringType{}, "start_at": basetypes.StringType{}, }, @@ -995,430 +973,6 @@ func (v PeriodsValue) Type(ctx context.Context) attr.Type { } func (v PeriodsValue) AttributeTypes(ctx context.Context) map[string]attr.Type { - return map[string]attr.Type{ - "amount": basetypes.StringType{}, - "end_at": basetypes.StringType{}, - "start_at": basetypes.StringType{}, - } -} - -var _ basetypes.ObjectTypable = PeriodsAttributesType{} - -type PeriodsAttributesType struct { - basetypes.ObjectType -} - -func (t PeriodsAttributesType) Equal(o attr.Type) bool { - other, ok := o.(PeriodsAttributesType) - - if !ok { - return false - } - - return t.ObjectType.Equal(other.ObjectType) -} - -func (t PeriodsAttributesType) String() string { - return "PeriodsAttributesType" -} - -func (t PeriodsAttributesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { - var diags diag.Diagnostics - - attributes := in.Attributes() - - amountAttribute, ok := attributes["amount"] - - if !ok { - diags.AddError( - "Attribute Missing", - `amount is missing from object`) - - return nil, diags - } - - amountVal, ok := amountAttribute.(basetypes.Float64Value) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`amount expected to be basetypes.Float64Value, was: %T`, amountAttribute)) - } - - endAtAttribute, ok := attributes["end_at"] - - if !ok { - diags.AddError( - "Attribute Missing", - `end_at is missing from object`) - - return nil, diags - } - - endAtVal, ok := endAtAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`end_at expected to be basetypes.StringValue, was: %T`, endAtAttribute)) - } - - startAtAttribute, ok := attributes["start_at"] - - if !ok { - diags.AddError( - "Attribute Missing", - `start_at is missing from object`) - - return nil, diags - } - - startAtVal, ok := startAtAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`start_at expected to be basetypes.StringValue, was: %T`, startAtAttribute)) - } - - if diags.HasError() { - return nil, diags - } - - return PeriodsAttributesValue{ - Amount: amountVal, - EndAt: endAtVal, - StartAt: startAtVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewPeriodsAttributesValueNull() PeriodsAttributesValue { - return PeriodsAttributesValue{ - state: attr.ValueStateNull, - } -} - -func NewPeriodsAttributesValueUnknown() PeriodsAttributesValue { - return PeriodsAttributesValue{ - state: attr.ValueStateUnknown, - } -} - -func NewPeriodsAttributesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PeriodsAttributesValue, diag.Diagnostics) { - var diags diag.Diagnostics - - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 - ctx := context.Background() - - for name, attributeType := range attributeTypes { - attribute, ok := attributes[name] - - if !ok { - diags.AddError( - "Missing PeriodsAttributesValue Attribute Value", - "While creating a PeriodsAttributesValue value, a missing attribute value was detected. "+ - "A PeriodsAttributesValue must contain values for all attributes, even if null or unknown. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("PeriodsAttributesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), - ) - - continue - } - - if !attributeType.Equal(attribute.Type(ctx)) { - diags.AddError( - "Invalid PeriodsAttributesValue Attribute Type", - "While creating a PeriodsAttributesValue value, an invalid attribute value was detected. "+ - "A PeriodsAttributesValue must use a matching attribute type for the value. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("PeriodsAttributesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ - fmt.Sprintf("PeriodsAttributesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), - ) - } - } - - for name := range attributes { - _, ok := attributeTypes[name] - - if !ok { - diags.AddError( - "Extra PeriodsAttributesValue Attribute Value", - "While creating a PeriodsAttributesValue value, an extra attribute value was detected. "+ - "A PeriodsAttributesValue must not contain values beyond the expected attribute types. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Extra PeriodsAttributesValue Attribute Name: %s", name), - ) - } - } - - if diags.HasError() { - return NewPeriodsAttributesValueUnknown(), diags - } - - amountAttribute, ok := attributes["amount"] - - if !ok { - diags.AddError( - "Attribute Missing", - `amount is missing from object`) - - return NewPeriodsAttributesValueUnknown(), diags - } - - amountVal, ok := amountAttribute.(basetypes.Float64Value) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`amount expected to be basetypes.Float64Value, was: %T`, amountAttribute)) - } - - endAtAttribute, ok := attributes["end_at"] - - if !ok { - diags.AddError( - "Attribute Missing", - `end_at is missing from object`) - - return NewPeriodsAttributesValueUnknown(), diags - } - - endAtVal, ok := endAtAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`end_at expected to be basetypes.StringValue, was: %T`, endAtAttribute)) - } - - startAtAttribute, ok := attributes["start_at"] - - if !ok { - diags.AddError( - "Attribute Missing", - `start_at is missing from object`) - - return NewPeriodsAttributesValueUnknown(), diags - } - - startAtVal, ok := startAtAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`start_at expected to be basetypes.StringValue, was: %T`, startAtAttribute)) - } - - if diags.HasError() { - return NewPeriodsAttributesValueUnknown(), diags - } - - return PeriodsAttributesValue{ - Amount: amountVal, - EndAt: endAtVal, - StartAt: startAtVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewPeriodsAttributesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PeriodsAttributesValue { - object, diags := NewPeriodsAttributesValue(attributeTypes, attributes) - - if diags.HasError() { - // This could potentially be added to the diag package. - diagsStrings := make([]string, 0, len(diags)) - - for _, diagnostic := range diags { - diagsStrings = append(diagsStrings, fmt.Sprintf( - "%s | %s | %s", - diagnostic.Severity(), - diagnostic.Summary(), - diagnostic.Detail())) - } - - panic("NewPeriodsAttributesValueMust received error(s): " + strings.Join(diagsStrings, "\n")) - } - - return object -} - -func (t PeriodsAttributesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - if in.Type() == nil { - return NewPeriodsAttributesValueNull(), nil - } - - if !in.Type().Equal(t.TerraformType(ctx)) { - return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) - } - - if !in.IsKnown() { - return NewPeriodsAttributesValueUnknown(), nil - } - - if in.IsNull() { - return NewPeriodsAttributesValueNull(), nil - } - - attributes := map[string]attr.Value{} - - val := map[string]tftypes.Value{} - - err := in.As(&val) - - if err != nil { - return nil, err - } - - for k, v := range val { - a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) - - if err != nil { - return nil, err - } - - attributes[k] = a - } - - return NewPeriodsAttributesValueMust(PeriodsAttributesValue{}.AttributeTypes(ctx), attributes), nil -} - -func (t PeriodsAttributesType) ValueType(ctx context.Context) attr.Value { - return PeriodsAttributesValue{} -} - -var _ basetypes.ObjectValuable = PeriodsAttributesValue{} - -type PeriodsAttributesValue struct { - Amount basetypes.Float64Value `tfsdk:"amount"` - EndAt basetypes.StringValue `tfsdk:"end_at"` - StartAt basetypes.StringValue `tfsdk:"start_at"` - state attr.ValueState -} - -func (v PeriodsAttributesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { - attrTypes := make(map[string]tftypes.Type, 3) - - var val tftypes.Value - var err error - - attrTypes["amount"] = basetypes.Float64Type{}.TerraformType(ctx) - attrTypes["end_at"] = basetypes.StringType{}.TerraformType(ctx) - attrTypes["start_at"] = basetypes.StringType{}.TerraformType(ctx) - - objectType := tftypes.Object{AttributeTypes: attrTypes} - - switch v.state { - case attr.ValueStateKnown: - vals := make(map[string]tftypes.Value, 3) - - val, err = v.Amount.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["amount"] = val - - val, err = v.EndAt.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["end_at"] = val - - val, err = v.StartAt.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["start_at"] = val - - if err := tftypes.ValidateValue(objectType, vals); err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - return tftypes.NewValue(objectType, vals), nil - case attr.ValueStateNull: - return tftypes.NewValue(objectType, nil), nil - case attr.ValueStateUnknown: - return tftypes.NewValue(objectType, tftypes.UnknownValue), nil - default: - panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) - } -} - -func (v PeriodsAttributesValue) IsNull() bool { - return v.state == attr.ValueStateNull -} - -func (v PeriodsAttributesValue) IsUnknown() bool { - return v.state == attr.ValueStateUnknown -} - -func (v PeriodsAttributesValue) String() string { - return "PeriodsAttributesValue" -} - -func (v PeriodsAttributesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { - var diags diag.Diagnostics - - objVal, diags := types.ObjectValue( - map[string]attr.Type{ - "amount": basetypes.Float64Type{}, - "end_at": basetypes.StringType{}, - "start_at": basetypes.StringType{}, - }, - map[string]attr.Value{ - "amount": v.Amount, - "end_at": v.EndAt, - "start_at": v.StartAt, - }) - - return objVal, diags -} - -func (v PeriodsAttributesValue) Equal(o attr.Value) bool { - other, ok := o.(PeriodsAttributesValue) - - if !ok { - return false - } - - if v.state != other.state { - return false - } - - if v.state != attr.ValueStateKnown { - return true - } - - if !v.Amount.Equal(other.Amount) { - return false - } - - if !v.EndAt.Equal(other.EndAt) { - return false - } - - if !v.StartAt.Equal(other.StartAt) { - return false - } - - return true -} - -func (v PeriodsAttributesValue) Type(ctx context.Context) attr.Type { - return PeriodsAttributesType{ - basetypes.ObjectType{ - AttrTypes: v.AttributeTypes(ctx), - }, - } -} - -func (v PeriodsAttributesValue) AttributeTypes(ctx context.Context) map[string]attr.Type { return map[string]attr.Type{ "amount": basetypes.Float64Type{}, "end_at": basetypes.StringType{},