diff --git a/docs/data-sources/observability_instance.md b/docs/data-sources/observability_instance.md index dcd6cad7..c31a946c 100644 --- a/docs/data-sources/observability_instance.md +++ b/docs/data-sources/observability_instance.md @@ -96,7 +96,7 @@ Read-Only: Read-Only: - `auth_identity` (String) SMTP authentication information. Must be a valid email address -- `auth_password` (String) SMTP authentication password. +- `auth_password` (String, Sensitive) SMTP authentication password. - `auth_username` (String) SMTP authentication username. - `from` (String) The sender email address. Must be a valid email address - `smart_host` (String) The SMTP host through which emails are sent. @@ -119,8 +119,9 @@ Read-Only: Read-Only: +- `google_chat` (Boolean) Google Chat webhooks require special handling, set this to true if the webhook is for Google Chat. - `ms_teams` (Boolean) Microsoft Teams webhooks require special handling, set this to true if the webhook is for Microsoft Teams. -- `url` (String) The endpoint to send HTTP POST requests to. Must be a valid URL +- `url` (String, Sensitive) The endpoint to send HTTP POST requests to. Must be a valid URL diff --git a/docs/resources/observability_instance.md b/docs/resources/observability_instance.md index 5dab17f1..5762b1d0 100644 --- a/docs/resources/observability_instance.md +++ b/docs/resources/observability_instance.md @@ -101,7 +101,7 @@ Optional: Optional: - `auth_identity` (String) SMTP authentication information. Must be a valid email address -- `auth_password` (String) SMTP authentication password. +- `auth_password` (String, Sensitive) SMTP authentication password. - `auth_username` (String) SMTP authentication username. - `from` (String) The sender email address. Must be a valid email address - `smart_host` (String) The SMTP host through which emails are sent. @@ -124,8 +124,9 @@ Optional: Optional: +- `google_chat` (Boolean) Google Chat webhooks require special handling, set this to true if the webhook is for Google Chat. - `ms_teams` (Boolean) Microsoft Teams webhooks require special handling, set this to true if the webhook is for Microsoft Teams. -- `url` (String) The endpoint to send HTTP POST requests to. Must be a valid URL +- `url` (String, Sensitive) The endpoint to send HTTP POST requests to. Must be a valid URL diff --git a/go.mod b/go.mod index 36cdd6ed..4a96d02c 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/modelserving v0.5.1 github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.2 github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1 - github.com/stackitcloud/stackit-sdk-go/services/observability v0.10.0 + github.com/stackitcloud/stackit-sdk-go/services/observability v0.11.0 github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.1 github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.2.1 github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.25.1 diff --git a/go.sum b/go.sum index 2ce8dfb2..9ba1190c 100644 --- a/go.sum +++ b/go.sum @@ -176,8 +176,8 @@ github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.2 h1:BQ+qAkVS/a github.com/stackitcloud/stackit-sdk-go/services/mongodbflex v1.5.2/go.mod h1:oc8Mpwl7O6EZwG0YxfhOzNCJwNQBWK5rFh764OtxoMY= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1 h1:4jsFLbDVEosYTgQz6lPds1E9KDOiHwjuhWqcG+lo5B4= github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.3.1/go.mod h1:j1SHAS5lN8F9b/iPUOfjAl9QAA9tOT7NKOiDEzcM2zc= -github.com/stackitcloud/stackit-sdk-go/services/observability v0.10.0 h1:SIctDqGprEoZArWaTds7hzQyh8Pqaz95Nmuj/1QuDEQ= -github.com/stackitcloud/stackit-sdk-go/services/observability v0.10.0/go.mod h1:tJEOi6L0le4yQZPGwalup/PZ13gqs1aCQDqlUs2cYW0= +github.com/stackitcloud/stackit-sdk-go/services/observability v0.11.0 h1:5BhZ/Ry3KWmhjBTVFBdLfVjPxxUOvVJuhGY0bnGRS7I= +github.com/stackitcloud/stackit-sdk-go/services/observability v0.11.0/go.mod h1:tJEOi6L0le4yQZPGwalup/PZ13gqs1aCQDqlUs2cYW0= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.1 h1:50n87uZn0EvSP9hJGLqd3Wm2hfqbyh7BMGGCk7axgqA= github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.24.1/go.mod h1:jfguuSPa56Z5Bzs/Xg/CI37XzPo5Zn5lzC5LhfuT8Qc= github.com/stackitcloud/stackit-sdk-go/services/postgresflex v1.2.1 h1:K8vXele3U6b5urcSIpq21EkVblWfPDY3eMPSuQ48TkI= diff --git a/stackit/internal/services/observability/instance/datasource.go b/stackit/internal/services/observability/instance/datasource.go index 7dcb92cc..3e7891d9 100644 --- a/stackit/internal/services/observability/instance/datasource.go +++ b/stackit/internal/services/observability/instance/datasource.go @@ -210,6 +210,7 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques "auth_password": schema.StringAttribute{ Description: "SMTP authentication password.", Computed: true, + Sensitive: true, }, "auth_username": schema.StringAttribute{ Description: "SMTP authentication username.", @@ -262,11 +263,16 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques "url": schema.StringAttribute{ Description: "The endpoint to send HTTP POST requests to. Must be a valid URL", Computed: true, + Sensitive: true, }, "ms_teams": schema.BoolAttribute{ Description: "Microsoft Teams webhooks require special handling, set this to true if the webhook is for Microsoft Teams.", Computed: true, }, + "google_chat": schema.BoolAttribute{ + Description: "Google Chat webhooks require special handling, set this to true if the webhook is for Google Chat.", + Computed: true, + }, }, }, }, diff --git a/stackit/internal/services/observability/instance/resource.go b/stackit/internal/services/observability/instance/resource.go index 5499ff41..4e6fe244 100644 --- a/stackit/internal/services/observability/instance/resource.go +++ b/stackit/internal/services/observability/instance/resource.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -202,13 +203,15 @@ var opsgenieConfigsTypes = map[string]attr.Type{ // Struct corresponding to Model.AlertConfig.receivers.webHooksConfigs type webHooksConfigsModel struct { - Url types.String `tfsdk:"url"` - MsTeams types.Bool `tfsdk:"ms_teams"` + Url types.String `tfsdk:"url"` + MsTeams types.Bool `tfsdk:"ms_teams"` + GoogleChat types.Bool `tfsdk:"google_chat"` } var webHooksConfigsTypes = map[string]attr.Type{ - "url": types.StringType, - "ms_teams": types.BoolType, + "url": types.StringType, + "ms_teams": types.BoolType, + "google_chat": types.BoolType, } var routeDescriptions = map[string]string{ @@ -591,6 +594,7 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r "auth_password": schema.StringAttribute{ Description: "SMTP authentication password.", Optional: true, + Sensitive: true, }, "auth_username": schema.StringAttribute{ Description: "SMTP authentication username.", @@ -645,14 +649,26 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r listvalidator.SizeAtLeast(1), }, NestedObject: schema.NestedAttributeObject{ + Validators: []validator.Object{ + WebhookConfigMutuallyExclusive(), + }, Attributes: map[string]schema.Attribute{ "url": schema.StringAttribute{ Description: "The endpoint to send HTTP POST requests to. Must be a valid URL", Optional: true, + Sensitive: true, }, "ms_teams": schema.BoolAttribute{ Description: "Microsoft Teams webhooks require special handling, set this to true if the webhook is for Microsoft Teams.", Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "google_chat": schema.BoolAttribute{ + Description: "Google Chat webhooks require special handling, set this to true if the webhook is for Google Chat.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), }, }, }, @@ -1687,9 +1703,13 @@ func mapReceiversToAttributes(ctx context.Context, respReceivers *[]observabilit webhooksConfigList := []attr.Value{} if receiver.WebHookConfigs != nil { for _, webhookConfig := range *receiver.WebHookConfigs { + msTeamsValue := types.BoolPointerValue(webhookConfig.MsTeams) + googleChatValue := types.BoolPointerValue(webhookConfig.GoogleChat) + webHookConfigsMap := map[string]attr.Value{ - "url": types.StringPointerValue(webhookConfig.Url), - "ms_teams": types.BoolPointerValue(webhookConfig.MsTeams), + "url": types.StringPointerValue(webhookConfig.Url), + "ms_teams": msTeamsValue, + "google_chat": googleChatValue, } webHookConfigsModel, diags := types.ObjectValue(webHooksConfigsTypes, webHookConfigsMap) if diags.HasError() { @@ -2040,8 +2060,9 @@ func toReceiverPayload(ctx context.Context, model *alertConfigModel) (*[]observa for i := range receiverWebHooksConfigs { webHooksConfig := receiverWebHooksConfigs[i] payloadWebHooksConfigs = append(payloadWebHooksConfigs, observability.CreateAlertConfigReceiverPayloadWebHookConfigsInner{ - Url: conversion.StringValueToPointer(webHooksConfig.Url), - MsTeams: conversion.BoolValueToPointer(webHooksConfig.MsTeams), + Url: conversion.StringValueToPointer(webHooksConfig.Url), + MsTeams: conversion.BoolValueToPointer(webHooksConfig.MsTeams), + GoogleChat: conversion.BoolValueToPointer(webHooksConfig.GoogleChat), }) } receiverPayload.WebHookConfigs = &payloadWebHooksConfigs @@ -2214,3 +2235,51 @@ func setMetricsRetentions(ctx context.Context, state *tfsdk.State, model *Model) func setAlertConfig(ctx context.Context, state *tfsdk.State, model *Model) diag.Diagnostics { return state.SetAttribute(ctx, path.Root("alert_config"), model.AlertConfig) } + +type webhookConfigMutuallyExclusive struct{} + +func (v webhookConfigMutuallyExclusive) Description(_ context.Context) string { + return "ms_teams and google_chat cannot both be true" +} + +func (v webhookConfigMutuallyExclusive) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v webhookConfigMutuallyExclusive) ValidateObject(_ context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) { //nolint:gocritic // req parameter signature required by validator.Object interface + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + attributes := req.ConfigValue.Attributes() + + msTeamsAttr, msTeamsExists := attributes["ms_teams"] + googleChatAttr, googleChatExists := attributes["google_chat"] + + if !msTeamsExists || !googleChatExists { + return + } + + if msTeamsAttr.IsNull() || msTeamsAttr.IsUnknown() || googleChatAttr.IsNull() || googleChatAttr.IsUnknown() { + return + } + + msTeamsValue, ok1 := msTeamsAttr.(types.Bool) + googleChatValue, ok2 := googleChatAttr.(types.Bool) + + if !ok1 || !ok2 { + return + } + + if msTeamsValue.ValueBool() && googleChatValue.ValueBool() { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Webhook Configuration", + "Both ms_teams and google_chat cannot be set to true at the same time. Only one can be true.", + ) + } +} + +func WebhookConfigMutuallyExclusive() validator.Object { + return webhookConfigMutuallyExclusive{} +} diff --git a/stackit/internal/services/observability/instance/resource_test.go b/stackit/internal/services/observability/instance/resource_test.go index 7ecffcec..3b52e763 100644 --- a/stackit/internal/services/observability/instance/resource_test.go +++ b/stackit/internal/services/observability/instance/resource_test.go @@ -44,8 +44,9 @@ func fixtureOpsGenieConfigsModel() basetypes.ListValue { func fixtureWebHooksConfigsModel() basetypes.ListValue { return types.ListValueMust(types.ObjectType{AttrTypes: webHooksConfigsTypes}, []attr.Value{ types.ObjectValueMust(webHooksConfigsTypes, map[string]attr.Value{ - "url": types.StringValue("http://example.com"), - "ms_teams": types.BoolValue(true), + "url": types.StringValue("http://example.com"), + "ms_teams": types.BoolValue(true), + "google_chat": types.BoolValue(true), }), }) } @@ -150,8 +151,9 @@ func fixtureOpsGenieConfigsPayload() observability.CreateAlertConfigReceiverPayl func fixtureWebHooksConfigsPayload() observability.CreateAlertConfigReceiverPayloadWebHookConfigsInner { return observability.CreateAlertConfigReceiverPayloadWebHookConfigsInner{ - Url: utils.Ptr("http://example.com"), - MsTeams: utils.Ptr(true), + Url: utils.Ptr("http://example.com"), + MsTeams: utils.Ptr(true), + GoogleChat: utils.Ptr(true), } } @@ -231,8 +233,9 @@ func fixtureOpsGenieConfigsResponse() observability.OpsgenieConfig { func fixtureWebHooksConfigsResponse() observability.WebHook { return observability.WebHook{ - Url: utils.Ptr("http://example.com"), - MsTeams: utils.Ptr(true), + Url: utils.Ptr("http://example.com"), + MsTeams: utils.Ptr(true), + GoogleChat: utils.Ptr(true), } } diff --git a/stackit/internal/services/observability/observability_acc_test.go b/stackit/internal/services/observability/observability_acc_test.go index 2d3d791d..20ff003c 100644 --- a/stackit/internal/services/observability/observability_acc_test.go +++ b/stackit/internal/services/observability/observability_acc_test.go @@ -87,6 +87,7 @@ var testConfigVarsMax = config.Variables{ "opsgenie_priority": config.StringVariable("P3"), "webhook_configs_url": config.StringVariable("https://example.com"), "ms_teams": config.StringVariable("true"), + "google_chat": config.StringVariable("false"), "group_by": config.StringVariable("alertname"), "group_interval": config.StringVariable("10m"), "group_wait": config.StringVariable("1m"), @@ -130,6 +131,9 @@ func configVarsMaxUpdated() config.Variables { tempConfig["alert_rule_expression"] = config.StringVariable(alert_rule_expression_updated) tempConfig["logalertgroup_interval"] = config.StringVariable("1h") tempConfig["logalertgroup_expression"] = config.StringVariable(logalertgroup_expression_updated) + tempConfig["webhook_configs_url"] = config.StringVariable("https://chat.googleapis.com/api") + tempConfig["ms_teams"] = config.StringVariable("false") + tempConfig["google_chat"] = config.StringVariable("true") return tempConfig } @@ -510,6 +514,7 @@ func TestAccResourceMax(t *testing.T) { resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.url", testutil.ConvertConfigVariable(testConfigVarsMax["webhook_configs_url"])), resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.ms_teams", testutil.ConvertConfigVariable(testConfigVarsMax["ms_teams"])), + resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.google_chat", testutil.ConvertConfigVariable(testConfigVarsMax["google_chat"])), resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.group_by.0", testutil.ConvertConfigVariable(testConfigVarsMax["group_by"])), resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.group_interval", testutil.ConvertConfigVariable(testConfigVarsMax["group_interval"])), @@ -672,6 +677,7 @@ func TestAccResourceMax(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.url", testutil.ConvertConfigVariable(testConfigVarsMax["webhook_configs_url"])), resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.ms_teams", testutil.ConvertConfigVariable(testConfigVarsMax["ms_teams"])), + resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.google_chat", testutil.ConvertConfigVariable(testConfigVarsMax["google_chat"])), resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.group_by.0", testutil.ConvertConfigVariable(testConfigVarsMax["group_by"])), resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.group_interval", testutil.ConvertConfigVariable(testConfigVarsMax["group_interval"])), @@ -893,8 +899,9 @@ func TestAccResourceMax(t *testing.T) { resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.opsgenie_configs.0.api_url", testutil.ConvertConfigVariable(testConfigVarsMax["opsgenie_api_url"])), resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.opsgenie_configs.0.priority", testutil.ConvertConfigVariable(testConfigVarsMax["opsgenie_priority"])), - resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.url", testutil.ConvertConfigVariable(testConfigVarsMax["webhook_configs_url"])), - resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.ms_teams", testutil.ConvertConfigVariable(testConfigVarsMax["ms_teams"])), + resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.url", testutil.ConvertConfigVariable(configVarsMaxUpdated()["webhook_configs_url"])), + resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.ms_teams", testutil.ConvertConfigVariable(configVarsMaxUpdated()["ms_teams"])), + resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.receivers.0.webhooks_configs.0.google_chat", testutil.ConvertConfigVariable(configVarsMaxUpdated()["google_chat"])), resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.group_by.0", testutil.ConvertConfigVariable(testConfigVarsMax["group_by"])), resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.group_interval", testutil.ConvertConfigVariable(testConfigVarsMax["group_interval"])), diff --git a/stackit/internal/services/observability/testdata/resource-max.tf b/stackit/internal/services/observability/testdata/resource-max.tf index 133c0185..59f91d71 100644 --- a/stackit/internal/services/observability/testdata/resource-max.tf +++ b/stackit/internal/services/observability/testdata/resource-max.tf @@ -29,6 +29,7 @@ variable "opsgenie_api_url" {} variable "opsgenie_priority" {} variable "webhook_configs_url" {} variable "ms_teams" {} +variable "google_chat" {} variable "group_by" {} variable "group_interval" {} variable "group_wait" {} @@ -125,6 +126,7 @@ resource "stackit_observability_instance" "instance" { { url = var.webhook_configs_url ms_teams = var.ms_teams + google_chat = var.google_chat } ] },