diff --git a/Makefile b/Makefile index 229cbba..65a4289 100644 --- a/Makefile +++ b/Makefile @@ -6,4 +6,4 @@ generate: @echo "Generating Terraform code from OpenAPI spec" tfplugingen-framework generate resources --input spec.json --output vantage tfplugingen-framework generate data-sources --input spec.json --output vantage - rm tmp.json + rm spec.json tmp.json diff --git a/examples/data_sources/report_alerts.tf b/examples/data_sources/report_alerts.tf new file mode 100644 index 0000000..8f2da1d --- /dev/null +++ b/examples/data_sources/report_alerts.tf @@ -0,0 +1,5 @@ +data "vantage_report_alerts" "all" {} + +output "all_report_alerts" { + value = data.vantage_report_alerts.all +} \ No newline at end of file diff --git a/examples/resources/vantage_report_alert/resource.tf b/examples/resources/vantage_report_alert/resource.tf new file mode 100644 index 0000000..e2c1f3a --- /dev/null +++ b/examples/resources/vantage_report_alert/resource.tf @@ -0,0 +1,5 @@ +resource "vantage_report_alert" "demo_report_alert" { + cost_report_token = "rpt_47c3254c790e9351" + threshold = 10 + recipient_channels = ["#alerts"] +} \ No newline at end of file diff --git a/generator.yaml b/generator.yaml index 6f32ff3..5fc2041 100644 --- a/generator.yaml +++ b/generator.yaml @@ -1,6 +1,28 @@ provider: name: vantage +resources: + report_alert: + create: + path: /report_alerts + method: POST + read: + path: /report_alerts/{report_alert_token} + method: GET + update: + path: /report_alerts/{report_alert_token} + method: PUT + delete: + path: /report_alerts/{report_alert_token} + method: DELETE + schema: + attributes: + overrides: + token: + description: The token of the report alert + aliases: + report_alert_token: token + data_sources: report_notifications: read: @@ -29,3 +51,12 @@ data_sources: - limit - links - page + report_alerts: + read: + path: /report_alerts + method: GET + schema: + ignores: + - limit + - links + - page diff --git a/vantage/client.go b/vantage/client.go index bfe0a4f..332885e 100644 --- a/vantage/client.go +++ b/vantage/client.go @@ -1,6 +1,7 @@ package vantage import ( + "context" "fmt" "net/http" "net/url" @@ -120,3 +121,11 @@ func fromStringsValue(s []types.String) []string { return out } + +func stringListFrom(in []string) (types.List, diag.Diagnostics) { + values := make([]types.String, 0, len(in)) + for _, v := range in { + values = append(values, types.StringValue(v)) + } + return types.ListValueFrom(context.Background(), types.StringType, values) +} diff --git a/vantage/datasource_report_alerts/report_alerts_data_source_gen.go b/vantage/datasource_report_alerts/report_alerts_data_source_gen.go new file mode 100644 index 0000000..b852a8b --- /dev/null +++ b/vantage/datasource_report_alerts/report_alerts_data_source_gen.go @@ -0,0 +1,770 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package datasource_report_alerts + +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 ReportAlertsDataSourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "report_alerts": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "cost_report_token": schema.StringAttribute{ + Computed: true, + Description: "The token for the CostReport the ReportAlert is associated with.", + MarkdownDescription: "The token for the CostReport the ReportAlert is associated with.", + }, + "created_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the ReportAlert was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the ReportAlert was created. ISO 8601 Formatted.", + }, + "recipient_channels": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + Description: "The channels that the alert is sent to.", + MarkdownDescription: "The channels that the alert is sent to.", + }, + "threshold": schema.Int64Attribute{ + Computed: true, + Description: "The threshold amount that must be met for the alert to fire.", + MarkdownDescription: "The threshold amount that must be met for the alert to fire.", + }, + "token": schema.StringAttribute{ + Computed: true, + }, + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the ReportAlert was last updated at. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the ReportAlert was last updated at. ISO 8601 Formatted.", + }, + "user_tokens": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + Description: "The tokens of the users that receive the alert.", + MarkdownDescription: "The tokens of the users that receive the alert.", + }, + }, + CustomType: ReportAlertsType{ + ObjectType: types.ObjectType{ + AttrTypes: ReportAlertsValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + }, + }, + } +} + +type ReportAlertsModel struct { + ReportAlerts types.List `tfsdk:"report_alerts"` +} + +var _ basetypes.ObjectTypable = ReportAlertsType{} + +type ReportAlertsType struct { + basetypes.ObjectType +} + +func (t ReportAlertsType) Equal(o attr.Type) bool { + other, ok := o.(ReportAlertsType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t ReportAlertsType) String() string { + return "ReportAlertsType" +} + +func (t ReportAlertsType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + 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)) + } + + recipientChannelsAttribute, ok := attributes["recipient_channels"] + + if !ok { + diags.AddError( + "Attribute Missing", + `recipient_channels is missing from object`) + + return nil, diags + } + + recipientChannelsVal, ok := recipientChannelsAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`recipient_channels expected to be basetypes.ListValue, was: %T`, recipientChannelsAttribute)) + } + + thresholdAttribute, ok := attributes["threshold"] + + if !ok { + diags.AddError( + "Attribute Missing", + `threshold is missing from object`) + + return nil, diags + } + + thresholdVal, ok := thresholdAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`threshold expected to be basetypes.Int64Value, was: %T`, thresholdAttribute)) + } + + 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)) + } + + updatedAtAttribute, ok := attributes["updated_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `updated_at is missing from object`) + + return nil, diags + } + + updatedAtVal, ok := updatedAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`updated_at expected to be basetypes.StringValue, was: %T`, updatedAtAttribute)) + } + + userTokensAttribute, ok := attributes["user_tokens"] + + if !ok { + diags.AddError( + "Attribute Missing", + `user_tokens is missing from object`) + + return nil, diags + } + + userTokensVal, ok := userTokensAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`user_tokens expected to be basetypes.ListValue, was: %T`, userTokensAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return ReportAlertsValue{ + CostReportToken: costReportTokenVal, + CreatedAt: createdAtVal, + RecipientChannels: recipientChannelsVal, + Threshold: thresholdVal, + Token: tokenVal, + UpdatedAt: updatedAtVal, + UserTokens: userTokensVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewReportAlertsValueNull() ReportAlertsValue { + return ReportAlertsValue{ + state: attr.ValueStateNull, + } +} + +func NewReportAlertsValueUnknown() ReportAlertsValue { + return ReportAlertsValue{ + state: attr.ValueStateUnknown, + } +} + +func NewReportAlertsValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (ReportAlertsValue, 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 ReportAlertsValue Attribute Value", + "While creating a ReportAlertsValue value, a missing attribute value was detected. "+ + "A ReportAlertsValue 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("ReportAlertsValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid ReportAlertsValue Attribute Type", + "While creating a ReportAlertsValue value, an invalid attribute value was detected. "+ + "A ReportAlertsValue 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("ReportAlertsValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("ReportAlertsValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra ReportAlertsValue Attribute Value", + "While creating a ReportAlertsValue value, an extra attribute value was detected. "+ + "A ReportAlertsValue 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 ReportAlertsValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewReportAlertsValueUnknown(), diags + } + + costReportTokenAttribute, ok := attributes["cost_report_token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `cost_report_token is missing from object`) + + return NewReportAlertsValueUnknown(), 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 NewReportAlertsValueUnknown(), 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)) + } + + recipientChannelsAttribute, ok := attributes["recipient_channels"] + + if !ok { + diags.AddError( + "Attribute Missing", + `recipient_channels is missing from object`) + + return NewReportAlertsValueUnknown(), diags + } + + recipientChannelsVal, ok := recipientChannelsAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`recipient_channels expected to be basetypes.ListValue, was: %T`, recipientChannelsAttribute)) + } + + thresholdAttribute, ok := attributes["threshold"] + + if !ok { + diags.AddError( + "Attribute Missing", + `threshold is missing from object`) + + return NewReportAlertsValueUnknown(), diags + } + + thresholdVal, ok := thresholdAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`threshold expected to be basetypes.Int64Value, was: %T`, thresholdAttribute)) + } + + tokenAttribute, ok := attributes["token"] + + if !ok { + diags.AddError( + "Attribute Missing", + `token is missing from object`) + + return NewReportAlertsValueUnknown(), diags + } + + tokenVal, ok := tokenAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`token expected to be basetypes.StringValue, was: %T`, tokenAttribute)) + } + + updatedAtAttribute, ok := attributes["updated_at"] + + if !ok { + diags.AddError( + "Attribute Missing", + `updated_at is missing from object`) + + return NewReportAlertsValueUnknown(), diags + } + + updatedAtVal, ok := updatedAtAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`updated_at expected to be basetypes.StringValue, was: %T`, updatedAtAttribute)) + } + + userTokensAttribute, ok := attributes["user_tokens"] + + if !ok { + diags.AddError( + "Attribute Missing", + `user_tokens is missing from object`) + + return NewReportAlertsValueUnknown(), diags + } + + userTokensVal, ok := userTokensAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`user_tokens expected to be basetypes.ListValue, was: %T`, userTokensAttribute)) + } + + if diags.HasError() { + return NewReportAlertsValueUnknown(), diags + } + + return ReportAlertsValue{ + CostReportToken: costReportTokenVal, + CreatedAt: createdAtVal, + RecipientChannels: recipientChannelsVal, + Threshold: thresholdVal, + Token: tokenVal, + UpdatedAt: updatedAtVal, + UserTokens: userTokensVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewReportAlertsValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) ReportAlertsValue { + object, diags := NewReportAlertsValue(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("NewReportAlertsValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t ReportAlertsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewReportAlertsValueNull(), 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 NewReportAlertsValueUnknown(), nil + } + + if in.IsNull() { + return NewReportAlertsValueNull(), 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 NewReportAlertsValueMust(ReportAlertsValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t ReportAlertsType) ValueType(ctx context.Context) attr.Value { + return ReportAlertsValue{} +} + +var _ basetypes.ObjectValuable = ReportAlertsValue{} + +type ReportAlertsValue struct { + CostReportToken basetypes.StringValue `tfsdk:"cost_report_token"` + CreatedAt basetypes.StringValue `tfsdk:"created_at"` + RecipientChannels basetypes.ListValue `tfsdk:"recipient_channels"` + Threshold basetypes.Int64Value `tfsdk:"threshold"` + Token basetypes.StringValue `tfsdk:"token"` + UpdatedAt basetypes.StringValue `tfsdk:"updated_at"` + UserTokens basetypes.ListValue `tfsdk:"user_tokens"` + state attr.ValueState +} + +func (v ReportAlertsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 7) + + var val tftypes.Value + var err error + + attrTypes["cost_report_token"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["created_at"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["recipient_channels"] = basetypes.ListType{ + ElemType: types.StringType, + }.TerraformType(ctx) + attrTypes["threshold"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["token"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["updated_at"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["user_tokens"] = basetypes.ListType{ + ElemType: types.StringType, + }.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 7) + + 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.RecipientChannels.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["recipient_channels"] = val + + val, err = v.Threshold.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["threshold"] = val + + val, err = v.Token.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["token"] = val + + val, err = v.UpdatedAt.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["updated_at"] = val + + val, err = v.UserTokens.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["user_tokens"] = 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 ReportAlertsValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v ReportAlertsValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v ReportAlertsValue) String() string { + return "ReportAlertsValue" +} + +func (v ReportAlertsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + recipientChannelsVal, d := types.ListValue(types.StringType, v.RecipientChannels.Elements()) + + diags.Append(d...) + + if d.HasError() { + return types.ObjectUnknown(map[string]attr.Type{ + "cost_report_token": basetypes.StringType{}, + "created_at": basetypes.StringType{}, + "recipient_channels": basetypes.ListType{ + ElemType: types.StringType, + }, + "threshold": basetypes.Int64Type{}, + "token": basetypes.StringType{}, + "updated_at": basetypes.StringType{}, + "user_tokens": basetypes.ListType{ + ElemType: types.StringType, + }, + }), diags + } + + userTokensVal, d := types.ListValue(types.StringType, v.UserTokens.Elements()) + + diags.Append(d...) + + if d.HasError() { + return types.ObjectUnknown(map[string]attr.Type{ + "cost_report_token": basetypes.StringType{}, + "created_at": basetypes.StringType{}, + "recipient_channels": basetypes.ListType{ + ElemType: types.StringType, + }, + "threshold": basetypes.Int64Type{}, + "token": basetypes.StringType{}, + "updated_at": basetypes.StringType{}, + "user_tokens": basetypes.ListType{ + ElemType: types.StringType, + }, + }), diags + } + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "cost_report_token": basetypes.StringType{}, + "created_at": basetypes.StringType{}, + "recipient_channels": basetypes.ListType{ + ElemType: types.StringType, + }, + "threshold": basetypes.Int64Type{}, + "token": basetypes.StringType{}, + "updated_at": basetypes.StringType{}, + "user_tokens": basetypes.ListType{ + ElemType: types.StringType, + }, + }, + map[string]attr.Value{ + "cost_report_token": v.CostReportToken, + "created_at": v.CreatedAt, + "recipient_channels": recipientChannelsVal, + "threshold": v.Threshold, + "token": v.Token, + "updated_at": v.UpdatedAt, + "user_tokens": userTokensVal, + }) + + return objVal, diags +} + +func (v ReportAlertsValue) Equal(o attr.Value) bool { + other, ok := o.(ReportAlertsValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.CostReportToken.Equal(other.CostReportToken) { + return false + } + + if !v.CreatedAt.Equal(other.CreatedAt) { + return false + } + + if !v.RecipientChannels.Equal(other.RecipientChannels) { + return false + } + + if !v.Threshold.Equal(other.Threshold) { + return false + } + + if !v.Token.Equal(other.Token) { + return false + } + + if !v.UpdatedAt.Equal(other.UpdatedAt) { + return false + } + + if !v.UserTokens.Equal(other.UserTokens) { + return false + } + + return true +} + +func (v ReportAlertsValue) Type(ctx context.Context) attr.Type { + return ReportAlertsType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v ReportAlertsValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "cost_report_token": basetypes.StringType{}, + "created_at": basetypes.StringType{}, + "recipient_channels": basetypes.ListType{ + ElemType: types.StringType, + }, + "threshold": basetypes.Int64Type{}, + "token": basetypes.StringType{}, + "updated_at": basetypes.StringType{}, + "user_tokens": basetypes.ListType{ + ElemType: types.StringType, + }, + } +} diff --git a/vantage/provider.go b/vantage/provider.go index a6cf28c..1466bbc 100644 --- a/vantage/provider.go +++ b/vantage/provider.go @@ -153,6 +153,7 @@ func (p *vantageProvider) DataSources(_ context.Context) []func() datasource.Dat NewFinancialCommitmentReportsDataSource, NewKubernetesEfficiencyReportsDataSource, NewReportNotificationsDataSource, + NewReportAlertsDataSource, } } @@ -168,5 +169,6 @@ func (p *vantageProvider) Resources(_ context.Context) []func() resource.Resourc NewTeamResource, NewAccessGrantResource, NewReportNotificationResource, + NewReportAlertResource, } } diff --git a/vantage/report_alert_resource.go b/vantage/report_alert_resource.go new file mode 100644 index 0000000..5d4e8b6 --- /dev/null +++ b/vantage/report_alert_resource.go @@ -0,0 +1,213 @@ +package vantage + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/vantage-sh/terraform-provider-vantage/vantage/resource_report_alert" + modelsv2 "github.com/vantage-sh/vantage-go/vantagev2/models" + reportalertsv2 "github.com/vantage-sh/vantage-go/vantagev2/vantage/report_alerts" +) + +var _ resource.Resource = (*reportAlertResource)(nil) +var _ resource.ResourceWithConfigure = (*reportAlertResource)(nil) + +func NewReportAlertResource() resource.Resource { + return &reportAlertResource{} +} + +type reportAlertResource struct { + client *Client +} + +// Configure implements resource.ResourceWithConfigure. +func (r *reportAlertResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*Client) +} + +func (r *reportAlertResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_report_alert" +} + +func (r *reportAlertResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = resource_report_alert.ReportAlertResourceSchema(ctx) +} + +func (r *reportAlertResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data resource_report_alert.ReportAlertModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := reportalertsv2.NewCreateReportAlertParams() + + var userTokens []types.String + if !data.UserTokens.IsNull() && !data.UserTokens.IsUnknown() { + userTokens = make([]types.String, 0, len(data.UserTokens.Elements())) + resp.Diagnostics.Append(data.UserTokens.ElementsAs(ctx, &userTokens, false)...) + if resp.Diagnostics.HasError() { + return + } + } + + var recipientChannels []types.String + if !data.RecipientChannels.IsNull() && !data.RecipientChannels.IsUnknown() { + recipientChannels = make([]types.String, 0, len(data.RecipientChannels.Elements())) + resp.Diagnostics.Append(data.RecipientChannels.ElementsAs(ctx, &recipientChannels, false)...) + if resp.Diagnostics.HasError() { + return + } + } + + postReportAlert := &modelsv2.PostReportAlerts{ + CostReportToken: data.CostReportToken.ValueStringPointer(), + Threshold: int32(data.Threshold.ValueInt64()), + UserTokens: fromStringsValue(userTokens), + RecipientChannels: fromStringsValue(recipientChannels), + } + + params.WithReportAlerts(postReportAlert) + out, err := r.client.V2.ReportAlerts.CreateReportAlert(params, r.client.Auth) + + if err != nil { + if e, ok := err.(*reportalertsv2.CreateReportAlertBadRequest); ok { + handleBadRequest("Create Report Alert", &resp.Diagnostics, e.GetPayload()) + return + } + + handleError("Create Report Alert", &resp.Diagnostics, err) + return + } + + readPayloadIntoResourceModel(out.Payload, &data) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *reportAlertResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data resource_report_alert.ReportAlertModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := reportalertsv2.NewGetReportAlertParams() + params.SetReportAlertToken(data.Token.ValueString()) + out, err := r.client.V2.ReportAlerts.GetReportAlert(params, r.client.Auth) + if err != nil { + if _, ok := err.(*reportalertsv2.GetReportAlertNotFound); ok { + resp.State.RemoveResource(ctx) + return + } + + handleError("Get Report Alert", &resp.Diagnostics, err) + return + } + + readPayloadIntoResourceModel(out.Payload, &data) + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *reportAlertResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data resource_report_alert.ReportAlertModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := reportalertsv2.NewUpdateReportAlertParams() + params.SetReportAlertToken(data.Token.ValueString()) + + userTokensList, diag := types.ListValueFrom(ctx, types.StringType, data.UserTokens) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + var userTokens []string + userTokensList.ElementsAs(ctx, userTokens, false) + + recipientChannelsList, diag := types.ListValueFrom(ctx, types.StringType, data.RecipientChannels) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + var recipientChannels []string + recipientChannelsList.ElementsAs(ctx, recipientChannels, false) + + putReportAlert := &modelsv2.PutReportAlerts{ + Threshold: int32(data.Threshold.ValueInt64()), + UserTokens: userTokens, + RecipientChannels: recipientChannels, + } + + params.WithReportAlerts(putReportAlert) + out, err := r.client.V2.ReportAlerts.UpdateReportAlert(params, r.client.Auth) + if err != nil { + handleError("Update Report Alert", &resp.Diagnostics, err) + return + } + + readPayloadIntoResourceModel(out.Payload, &data) + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *reportAlertResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data resource_report_alert.ReportAlertModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := reportalertsv2.NewDeleteReportAlertParams() + params.SetReportAlertToken(data.Token.ValueString()) + _, err := r.client.V2.ReportAlerts.DeleteReportAlert(params, r.client.Auth) + if err != nil { + handleError("Delete Report Alert", &resp.Diagnostics, err) + } + // Delete API call logic +} + +func readPayloadIntoResourceModel(payload *modelsv2.ReportAlert, data *resource_report_alert.ReportAlertModel) { + data.Token = types.StringValue(payload.Token) + data.CostReportToken = types.StringValue(payload.CostReportToken) + data.CreatedAt = types.StringValue(payload.CreatedAt) + data.UpdatedAt = types.StringValue(payload.UpdatedAt) + data.Threshold = types.Int64Value((int64)(payload.Threshold)) + if payload.UserTokens != nil { + list, diag := stringListFrom(payload.UserTokens) + if diag.HasError() { + return + } + data.UserTokens = list + } + + if payload.RecipientChannels != nil { + list, diag := stringListFrom(payload.RecipientChannels) + if diag.HasError() { + return + } + data.RecipientChannels = list + } + +} diff --git a/vantage/report_alerts_data_source.go b/vantage/report_alerts_data_source.go new file mode 100644 index 0000000..46cd01a --- /dev/null +++ b/vantage/report_alerts_data_source.go @@ -0,0 +1,105 @@ +package vantage + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/vantage-sh/terraform-provider-vantage/vantage/datasource_report_alerts" + reportalertsv2 "github.com/vantage-sh/vantage-go/vantagev2/vantage/report_alerts" +) + +var _ datasource.DataSource = (*reportAlertsDataSource)(nil) +var _ datasource.DataSourceWithConfigure = (*reportAlertsDataSource)(nil) + +func NewReportAlertsDataSource() datasource.DataSource { + return &reportAlertsDataSource{} +} + +type reportAlertsDataSource struct { + client *Client +} + +// Configure implements datasource.DataSourceWithConfigure. +func (d *reportAlertsDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*Client) +} + +type reportAlertsDataSourceModel struct { + ReportAlerts []reportAlertDataSourceModel `tfsdk:"report_alerts"` +} + +type reportAlertDataSourceModel struct { + CostReportToken types.String `tfsdk:"cost_report_token"` + CreatedAt types.String `tfsdk:"created_at"` + RecipientChannels types.List `tfsdk:"recipient_channels"` + Threshold types.Int64 `tfsdk:"threshold"` + Token types.String `tfsdk:"token"` + UpdatedAt types.String `tfsdk:"updated_at"` + UserTokens types.List `tfsdk:"user_tokens"` +} + +func (d *reportAlertsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_report_alerts" +} + +func (d *reportAlertsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = datasource_report_alerts.ReportAlertsDataSourceSchema(ctx) +} + +func (d *reportAlertsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data reportAlertsDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + params := reportalertsv2.NewGetReportAlertsParams() + out, err := d.client.V2.ReportAlerts.GetReportAlerts(params, d.client.Auth) + if err != nil { + resp.Diagnostics.AddError( + "Unable to Get Vantage Report Alerts", + err.Error(), + ) + return + } + + reportAlerts := []reportAlertDataSourceModel{} + for _, reportAlert := range out.Payload.ReportAlerts { + userTokens, diag := types.ListValueFrom(ctx, types.StringType, reportAlert.UserTokens) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + recipientChannels, diag := types.ListValueFrom(ctx, types.StringType, reportAlert.RecipientChannels) + if diag.HasError() { + resp.Diagnostics.Append(diag...) + return + } + + reportAlerts = append(reportAlerts, reportAlertDataSourceModel{ + CostReportToken: types.StringValue(reportAlert.CostReportToken), + CreatedAt: types.StringValue(reportAlert.CreatedAt), + RecipientChannels: recipientChannels, + Threshold: types.Int64Value((int64)(reportAlert.Threshold)), + Token: types.StringValue(reportAlert.Token), + UpdatedAt: types.StringValue(reportAlert.UpdatedAt), + UserTokens: userTokens, + }) + data.ReportAlerts = reportAlerts + } + // Save data into Terraform state + diags := resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/vantage/resource_report_alert/report_alert_resource_gen.go b/vantage/resource_report_alert/report_alert_resource_gen.go new file mode 100644 index 0000000..b2883b8 --- /dev/null +++ b/vantage/resource_report_alert/report_alert_resource_gen.go @@ -0,0 +1,67 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package resource_report_alert + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func ReportAlertResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "cost_report_token": schema.StringAttribute{ + Required: true, + Description: "The token of the Cost Report folder that has the alert.", + MarkdownDescription: "The token of the Cost Report folder that has the alert.", + }, + "created_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the ReportAlert was created. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the ReportAlert was created. ISO 8601 Formatted.", + }, + "recipient_channels": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, + Description: "The Slack/MS Teams channels that receive the alert.", + MarkdownDescription: "The Slack/MS Teams channels that receive the alert.", + }, + "threshold": schema.Int64Attribute{ + Optional: true, + Computed: true, + Description: "The threshold amount that must be met for the alert to fire.", + MarkdownDescription: "The threshold amount that must be met for the alert to fire.", + }, + "token": schema.StringAttribute{ + Computed: true, + Description: "The token of the report alert", + MarkdownDescription: "The token of the report alert", + }, + "updated_at": schema.StringAttribute{ + Computed: true, + Description: "The date and time, in UTC, the ReportAlert was last updated at. ISO 8601 Formatted.", + MarkdownDescription: "The date and time, in UTC, the ReportAlert was last updated at. ISO 8601 Formatted.", + }, + "user_tokens": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Computed: true, + Description: "The tokens of the users that receive the alert.", + MarkdownDescription: "The tokens of the users that receive the alert.", + }, + }, + } +} + +type ReportAlertModel struct { + CostReportToken types.String `tfsdk:"cost_report_token"` + CreatedAt types.String `tfsdk:"created_at"` + RecipientChannels types.List `tfsdk:"recipient_channels"` + Threshold types.Int64 `tfsdk:"threshold"` + Token types.String `tfsdk:"token"` + UpdatedAt types.String `tfsdk:"updated_at"` + UserTokens types.List `tfsdk:"user_tokens"` +}