feat(observability): add google chat to webhooks (#961)

This commit is contained in:
Timo Bergen 2025-08-25 17:30:06 +02:00 committed by GitHub
parent 971cd27e4b
commit 0f8362ca74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 112 additions and 23 deletions

View file

@ -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

View file

@ -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

2
go.mod
View file

@ -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

4
go.sum
View file

@ -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=

View file

@ -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,
},
},
},
},

View file

@ -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{}
}

View file

@ -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),
}
}

View file

@ -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"])),

View file

@ -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
}
]
},