Alerting: Export of contact points to HCL (#75849)

* add compat layer to convert from Export model to "new" API models
This commit is contained in:
Yuri Tseretyan 2023-10-12 22:33:57 +01:00 committed by GitHub
parent 0a6d78f35e
commit 372082d254
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 932 additions and 12 deletions

View File

@ -548,15 +548,18 @@ func exportHcl(download bool, body definitions.AlertingFileExport) response.Resp
Body: &gr,
})
}
for idx, cp := range body.ContactPoints {
upd, err := ContactPointFromContactPointExport(cp)
if err != nil {
return response.Error(http.StatusInternalServerError, "failed to convert contact points to HCL", err)
}
resources = append(resources, hcl.Resource{
Type: "grafana_contact_point",
Name: fmt.Sprintf("contact_point_%d", idx),
Body: &upd,
})
}
// TODO implement support.
// for idx, cp := range ex.ContactPoints {
// resources = append(resources, resourceBlock{
// Type: "grafana_contact_point",
// Name: fmt.Sprintf("contact_point_%d", idx),
// Body: &cp,
// })
// }
for idx, cp := range body.Policies {
policy := cp.Policy
resources = append(resources, hcl.Resource{

View File

@ -0,0 +1,446 @@
package api
import (
"encoding/json"
"errors"
"fmt"
"strings"
"unsafe"
"github.com/grafana/alerting/notify"
"github.com/grafana/alerting/receivers"
jsoniter "github.com/json-iterator/go"
"github.com/modern-go/reflect2"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/util"
)
// ContactPointFromContactPointExport parses the database model of the contact point (group of integrations) where settings are represented in JSON,
// to strongly typed ContactPoint.
func ContactPointFromContactPointExport(rawContactPoint definitions.ContactPointExport) (definitions.ContactPoint, error) {
j := jsoniter.ConfigCompatibleWithStandardLibrary
j.RegisterExtension(&contactPointsExtension{})
contactPoint := definitions.ContactPoint{
Name: rawContactPoint.Name,
}
var errs []error
for _, rawIntegration := range rawContactPoint.Receivers {
err := parseIntegration(j, &contactPoint, rawIntegration.Type, rawIntegration.DisableResolveMessage, json.RawMessage(rawIntegration.Settings))
if err != nil {
// accumulate errors to report all at once.
errs = append(errs, fmt.Errorf("failed to parse %s integration (uid:%s): %w", rawIntegration.Type, rawIntegration.UID, err))
}
}
return contactPoint, errors.Join(errs...)
}
// ContactPointToContactPointExport converts definitions.ContactPoint to notify.APIReceiver.
// It uses special extension for json-iterator API that properly handles marshalling of some specific fields.
//
//nolint:gocyclo
func ContactPointToContactPointExport(cp definitions.ContactPoint) (notify.APIReceiver, error) {
j := jsoniter.ConfigCompatibleWithStandardLibrary
// use json iterator with custom extension that has special codec for some field.
// This is needed to keep the API models clean and convert from database model
j.RegisterExtension(&contactPointsExtension{})
var integration []*notify.GrafanaIntegrationConfig
var errs []error
for _, i := range cp.Alertmanager {
el, err := marshallIntegration(j, "prometheus-alertmanager", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Dingding {
el, err := marshallIntegration(j, "dingding", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Discord {
el, err := marshallIntegration(j, "discord", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Email {
el, err := marshallIntegration(j, "email", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Googlechat {
el, err := marshallIntegration(j, "googlechat", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Kafka {
el, err := marshallIntegration(j, "kafka", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Line {
el, err := marshallIntegration(j, "line", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Opsgenie {
el, err := marshallIntegration(j, "opsgenie", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Pagerduty {
el, err := marshallIntegration(j, "pagerduty", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.OnCall {
el, err := marshallIntegration(j, "oncall", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Pushover {
el, err := marshallIntegration(j, "pushover", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Sensugo {
el, err := marshallIntegration(j, "sensugo", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Slack {
el, err := marshallIntegration(j, "slack", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Teams {
el, err := marshallIntegration(j, "teams", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Telegram {
el, err := marshallIntegration(j, "telegram", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Threema {
el, err := marshallIntegration(j, "threema", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Victorops {
el, err := marshallIntegration(j, "victorops", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Webhook {
el, err := marshallIntegration(j, "webhook", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Wecom {
el, err := marshallIntegration(j, "wecom", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
for _, i := range cp.Webex {
el, err := marshallIntegration(j, "webex", i, i.DisableResolveMessage)
integration = append(integration, el)
if err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return notify.APIReceiver{}, errors.Join(errs...)
}
contactPoint := notify.APIReceiver{
ConfigReceiver: notify.ConfigReceiver{Name: cp.Name},
GrafanaIntegrations: notify.GrafanaIntegrations{Integrations: integration},
}
return contactPoint, nil
}
// marshallIntegration converts the API model integration to the storage model that contains settings in the JSON format.
// The secret fields are not encrypted.
func marshallIntegration(json jsoniter.API, integrationType string, integration interface{}, disableResolveMessage *bool) (*notify.GrafanaIntegrationConfig, error) {
data, err := json.Marshal(integration)
if err != nil {
return nil, fmt.Errorf("failed to marshall integration '%s' to JSON: %w", integrationType, err)
}
e := &notify.GrafanaIntegrationConfig{
Type: integrationType,
Settings: data,
}
if disableResolveMessage != nil {
e.DisableResolveMessage = *disableResolveMessage
}
return e, nil
}
//nolint:gocyclo
func parseIntegration(json jsoniter.API, result *definitions.ContactPoint, receiverType string, disableResolveMessage bool, data json.RawMessage) error {
var err error
var disable *bool
if disableResolveMessage { // populate only if true
disable = util.Pointer(disableResolveMessage)
}
switch strings.ToLower(receiverType) {
case "prometheus-alertmanager":
integration := definitions.AlertmanagerIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Alertmanager = append(result.Alertmanager, integration)
}
case "dingding":
integration := definitions.DingdingIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Dingding = append(result.Dingding, integration)
}
case "discord":
integration := definitions.DiscordIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Discord = append(result.Discord, integration)
}
case "email":
integration := definitions.EmailIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Email = append(result.Email, integration)
}
case "googlechat":
integration := definitions.GooglechatIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Googlechat = append(result.Googlechat, integration)
}
case "kafka":
integration := definitions.KafkaIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Kafka = append(result.Kafka, integration)
}
case "line":
integration := definitions.LineIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Line = append(result.Line, integration)
}
case "opsgenie":
integration := definitions.OpsgenieIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Opsgenie = append(result.Opsgenie, integration)
}
case "pagerduty":
integration := definitions.PagerdutyIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Pagerduty = append(result.Pagerduty, integration)
}
case "oncall":
integration := definitions.OnCallIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.OnCall = append(result.OnCall, integration)
}
case "pushover":
integration := definitions.PushoverIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Pushover = append(result.Pushover, integration)
}
case "sensugo":
integration := definitions.SensugoIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Sensugo = append(result.Sensugo, integration)
}
case "slack":
integration := definitions.SlackIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Slack = append(result.Slack, integration)
}
case "teams":
integration := definitions.TeamsIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Teams = append(result.Teams, integration)
}
case "telegram":
integration := definitions.TelegramIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Telegram = append(result.Telegram, integration)
}
case "threema":
integration := definitions.ThreemaIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Threema = append(result.Threema, integration)
}
case "victorops":
integration := definitions.VictoropsIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Victorops = append(result.Victorops, integration)
}
case "webhook":
integration := definitions.WebhookIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Webhook = append(result.Webhook, integration)
}
case "wecom":
integration := definitions.WecomIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Wecom = append(result.Wecom, integration)
}
case "webex":
integration := definitions.WebexIntegration{DisableResolveMessage: disable}
if err = json.Unmarshal(data, &integration); err == nil {
result.Webex = append(result.Webex, integration)
}
default:
err = fmt.Errorf("integration %s is not supported", receiverType)
}
return err
}
// contactPointsExtension extends jsoniter with special codecs for some integrations' fields that are encoded differently in the legacy configuration.
type contactPointsExtension struct {
jsoniter.DummyExtension
}
func (c contactPointsExtension) UpdateStructDescriptor(structDescriptor *jsoniter.StructDescriptor) {
if structDescriptor.Type == reflect2.TypeOf(definitions.EmailIntegration{}) {
bind := structDescriptor.GetField("Addresses")
codec := &emailAddressCodec{}
bind.Decoder = codec
bind.Encoder = codec
}
if structDescriptor.Type == reflect2.TypeOf(definitions.PushoverIntegration{}) {
codec := &numberAsStringCodec{}
for _, field := range []string{"AlertingPriority", "OKPriority"} {
desc := structDescriptor.GetField(field)
desc.Decoder = codec
desc.Encoder = codec
}
// the same logic is in the pushover.NewConfig in alerting module
codec = &numberAsStringCodec{ignoreError: true}
for _, field := range []string{"Retry", "Expire"} {
desc := structDescriptor.GetField(field)
desc.Decoder = codec
desc.Encoder = codec
}
}
if structDescriptor.Type == reflect2.TypeOf(definitions.WebhookIntegration{}) {
codec := &numberAsStringCodec{ignoreError: true}
desc := structDescriptor.GetField("MaxAlerts")
desc.Decoder = codec
desc.Encoder = codec
}
if structDescriptor.Type == reflect2.TypeOf(definitions.OnCallIntegration{}) {
codec := &numberAsStringCodec{ignoreError: true}
desc := structDescriptor.GetField("MaxAlerts")
desc.Decoder = codec
desc.Encoder = codec
}
}
type emailAddressCodec struct{}
func (d *emailAddressCodec) IsEmpty(ptr unsafe.Pointer) bool {
f := *(*[]string)(ptr)
return len(f) == 0
}
func (d *emailAddressCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
f := *(*[]string)(ptr)
addresses := strings.Join(f, ";")
stream.WriteString(addresses)
}
func (d *emailAddressCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
s := iter.ReadString()
emails := strings.FieldsFunc(strings.Trim(s, "\""), func(r rune) bool {
switch r {
case ',', ';', '\n':
return true
}
return false
})
*((*[]string)(ptr)) = emails
}
// converts a string representation of a number to *int64
type numberAsStringCodec struct {
ignoreError bool // if true, then ignores the error and keeps value nil
}
func (d *numberAsStringCodec) IsEmpty(ptr unsafe.Pointer) bool {
return *((*(*int))(ptr)) == nil
}
func (d *numberAsStringCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) {
val := *((*(*int))(ptr))
if val == nil {
stream.WriteNil()
return
}
stream.WriteInt(*val)
}
func (d *numberAsStringCodec) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
valueType := iter.WhatIsNext()
var value int64
switch valueType {
case jsoniter.NumberValue:
value = iter.ReadInt64()
case jsoniter.StringValue:
var num receivers.OptionalNumber
err := num.UnmarshalJSON(iter.ReadStringAsSlice())
if err != nil {
iter.ReportError("numberAsStringCodec", fmt.Sprintf("failed to unmarshall string as OptionalNumber: %s", err.Error()))
}
if num.String() == "" {
return
}
value, err = num.Int64()
if err != nil {
if !d.ignoreError {
iter.ReportError("numberAsStringCodec", fmt.Sprintf("string does not represent an integer number: %s", err.Error()))
}
return
}
case jsoniter.NilValue:
iter.ReadNil()
return
default:
iter.ReportError("numberAsStringCodec", "not number or string")
}
*((*(*int64))(ptr)) = &value
}

View File

@ -0,0 +1,187 @@
package api
import (
"context"
"encoding/base64"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/grafana/alerting/notify"
receiversTesting "github.com/grafana/alerting/receivers/testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
)
// Test that conversion notify.APIReceiver -> definitions.ContactPoint -> notify.APIReceiver does not lose data
func TestContactPointFromContactPointExports(t *testing.T) {
getContactPointExport := func(t *testing.T, receiver *notify.APIReceiver) definitions.ContactPointExport {
export := make([]definitions.ReceiverExport, 0, len(receiver.Integrations))
for _, integrationConfig := range receiver.Integrations {
postable := &definitions.PostableGrafanaReceiver{
UID: integrationConfig.UID,
Name: integrationConfig.Name,
Type: integrationConfig.Type,
DisableResolveMessage: integrationConfig.DisableResolveMessage,
Settings: definitions.RawMessage(integrationConfig.Settings),
SecureSettings: integrationConfig.SecureSettings,
}
emb, err := provisioning.PostableGrafanaReceiverToEmbeddedContactPoint(
postable,
models.ProvenanceNone,
func(s string) string { // test configs are not encrypted but encoded
d, err := base64.StdEncoding.DecodeString(s)
require.NoError(t, err)
return string(d)
})
require.NoError(t, err)
ex, err := ReceiverExportFromEmbeddedContactPoint(emb)
require.NoError(t, err)
export = append(export, ex)
}
return definitions.ContactPointExport{
OrgID: 1,
Name: receiver.Name,
Receivers: export,
}
}
// use the configs for testing because they have all fields supported by integrations
for integrationType, cfg := range notify.AllKnownConfigsForTesting {
t.Run(integrationType, func(t *testing.T) {
recCfg := &notify.APIReceiver{
ConfigReceiver: notify.ConfigReceiver{Name: "test-receiver"},
GrafanaIntegrations: notify.GrafanaIntegrations{
Integrations: []*notify.GrafanaIntegrationConfig{
cfg.GetRawNotifierConfig("test"),
},
},
}
expected, err := notify.BuildReceiverConfiguration(context.Background(), recCfg, func(ctx context.Context, sjd map[string][]byte, key string, fallback string) string {
return receiversTesting.DecryptForTesting(sjd)(key, fallback)
})
require.NoError(t, err)
result, err := ContactPointFromContactPointExport(getContactPointExport(t, recCfg))
require.NoError(t, err)
back, err := ContactPointToContactPointExport(result)
require.NoError(t, err)
actual, err := notify.BuildReceiverConfiguration(context.Background(), &back, func(ctx context.Context, sjd map[string][]byte, key string, fallback string) string {
return receiversTesting.DecryptForTesting(sjd)(key, fallback)
})
require.NoError(t, err)
diff := cmp.Diff(expected, actual, cmp.FilterPath(func(path cmp.Path) bool {
return strings.Contains(path.String(), "Metadata.UID") ||
strings.Contains(path.String(), "Metadata.Name") ||
strings.Contains(path.String(), "WecomConfigs.Settings.EndpointURL") // This field is not exposed to user
}, cmp.Ignore()))
if len(diff) != 0 {
require.Failf(t, "The re-marshalled configuration does not match the expected one", diff)
}
})
}
t.Run("pushover optional numbers as string", func(t *testing.T) {
export := definitions.ContactPointExport{
Name: "test",
Receivers: []definitions.ReceiverExport{
{
Type: "pushover",
Settings: definitions.RawMessage(
`{
"priority": 1,
"okPriority": "2",
"expire": null,
"retry": "invalid"
}`),
},
},
}
result, err := ContactPointFromContactPointExport(export)
require.NoError(t, err)
require.Len(t, result.Pushover, 1)
require.Equal(t, int64(1), *result.Pushover[0].AlertingPriority)
require.Equal(t, int64(2), *result.Pushover[0].OKPriority)
require.Nil(t, result.Pushover[0].Expire)
require.Nil(t, result.Pushover[0].Retry)
})
t.Run("email with multiple addresses", func(t *testing.T) {
export := definitions.ContactPointExport{
Name: "test",
Receivers: []definitions.ReceiverExport{
{
Type: "email",
Settings: definitions.RawMessage(`{"addresses": "test@grafana.com,test2@grafana.com;test3@grafana.com\ntest4@granafa.com"}`),
},
},
}
result, err := ContactPointFromContactPointExport(export)
require.NoError(t, err)
require.Len(t, result.Email, 1)
require.EqualValues(t, []string{
"test@grafana.com",
"test2@grafana.com",
"test3@grafana.com",
"test4@granafa.com",
}, result.Email[0].Addresses)
})
t.Run("webhook with optional numbers as string", func(t *testing.T) {
export := definitions.ContactPointExport{
Name: "test",
Receivers: []definitions.ReceiverExport{
{
Type: "webhook",
Settings: definitions.RawMessage(`{ "maxAlerts" : "112" }`),
},
{
Type: "webhook",
Settings: definitions.RawMessage(`{ "maxAlerts" : "test" }`),
},
{
Type: "webhook",
Settings: definitions.RawMessage(`{ "maxAlerts" : null }`),
},
},
}
result, err := ContactPointFromContactPointExport(export)
require.NoError(t, err)
require.Len(t, result.Webhook, 3)
require.Equal(t, int64(112), *result.Webhook[0].MaxAlerts)
require.Nil(t, result.Webhook[1].MaxAlerts)
require.Nil(t, result.Webhook[2].MaxAlerts)
})
t.Run("oncall with optional numbers as string", func(t *testing.T) {
export := definitions.ContactPointExport{
Name: "test",
Receivers: []definitions.ReceiverExport{
{
Type: "oncall",
Settings: definitions.RawMessage(`{ "maxAlerts" : "112" }`),
},
{
Type: "oncall",
Settings: definitions.RawMessage(`{ "maxAlerts" : "test" }`),
},
{
Type: "oncall",
Settings: definitions.RawMessage(`{ "maxAlerts" : null }`),
},
},
}
result, err := ContactPointFromContactPointExport(export)
require.NoError(t, err)
require.Len(t, result.OnCall, 3)
require.Equal(t, int64(112), *result.OnCall[0].MaxAlerts)
require.Nil(t, result.OnCall[1].MaxAlerts)
require.Nil(t, result.OnCall[2].MaxAlerts)
})
}

View File

@ -0,0 +1,284 @@
package definitions
// This file contains API models of integrations that are supported by Grafana Managed Alerts.
// The models below match the Config models described in the module github.com/grafana/alerting, package 'receivers/**'
// as well as models described in Grafana Terraform Provider.
// Currently, they are used only for export to HCL but in the future we expand their scope.
// The consistency between models in the alerting module and this file is enforced by unit-tests.
//
// 1. JSON tags are used for unmarshalling from the definitions.PostableGrafanaReceiver.Settings.
// 2. YAML tags are not used but kept while copying of models from the alerting module
// 3. Each integration struct contains field 'DisableResolveMessage'. In Terraform provider the field is on the same level as the settings.
// Currently, HCL encoder does not support composition of structures or generic ones. This can be change after https://github.com/hashicorp/hcl/issues/290 is solved.
// 4. Sensitive fields have type Secret. Currently, this is done for information purpose and is not used anywhere.
// A string that contain sensitive information.
type Secret string // TODO implement masking fields when models are used
type AlertmanagerIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"` // TODO change when https://github.com/hashicorp/hcl/issues/290 is fixed
URL string `json:"url" yaml:"url" hcl:"url"`
User *string `json:"basicAuthUser,omitempty" yaml:"basicAuthUser,omitempty" hcl:"basic_auth_user"`
Password *Secret `json:"basicAuthPassword,omitempty" yaml:"basicAuthPassword,omitempty" hcl:"basic_auth_password"`
}
type DingdingIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL string `json:"url,omitempty" yaml:"url,omitempty" hcl:"url"`
MessageType *string `json:"msgType,omitempty" yaml:"msgType,omitempty" hcl:"message_type"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
}
type DiscordIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
WebhookURL Secret `json:"url" yaml:"url" hcl:"url"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
AvatarURL *string `json:"avatar_url,omitempty" yaml:"avatar_url,omitempty" hcl:"avatar_url"`
UseDiscordUsername *bool `json:"use_discord_username,omitempty" yaml:"use_discord_username,omitempty" hcl:"use_discord_username"`
}
type EmailIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
Addresses []string `json:"addresses" yaml:"addresses" hcl:"addresses"`
SingleEmail *bool `json:"singleEmail,omitempty" yaml:"singleEmail,omitempty" hcl:"single_email"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
Subject *string `json:"subject,omitempty" yaml:"subject,omitempty" hcl:"subject"`
}
type GooglechatIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL string `json:"url" yaml:"url" hcl:"url"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
}
type KafkaIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
Endpoint Secret `json:"kafkaRestProxy" yaml:"kafkaRestProxy" hcl:"rest_proxy_url"`
Topic string `json:"kafkaTopic" yaml:"kafkaTopic" hcl:"topic"`
Description *string `json:"description,omitempty" yaml:"description,omitempty" hcl:"description"`
Details *string `json:"details,omitempty" yaml:"details,omitempty" hcl:"details"`
Username *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"username"`
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"password"`
APIVersion *string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty" hcl:"api_version"`
KafkaClusterID *string `json:"kafkaClusterId,omitempty" yaml:"kafkaClusterId,omitempty" hcl:"cluster_id"`
}
type LineIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
Token Secret `json:"token" yaml:"token" hcl:"token"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Description *string `json:"description,omitempty" yaml:"description,omitempty" hcl:"description"`
}
type OnCallIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL string `json:"url" yaml:"url" hcl:"url"`
HTTPMethod *string `json:"httpMethod,omitempty" yaml:"httpMethod,omitempty" hcl:"http_method"`
MaxAlerts *int64 `json:"maxAlerts,omitempty" yaml:"maxAlerts,omitempty" hcl:"max_alerts"`
AuthorizationScheme *string `json:"authorization_scheme,omitempty" yaml:"authorization_scheme,omitempty" hcl:"authorization_scheme"`
AuthorizationCredentials *Secret `json:"authorization_credentials,omitempty" yaml:"authorization_credentials,omitempty" hcl:"authorization_credentials"`
User *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"basic_auth_user"`
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"basic_auth_password"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
}
type OpsgenieIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
APIKey Secret `json:"apiKey" yaml:"apiKey" hcl:"api_key"`
APIUrl *string `json:"apiUrl,omitempty" yaml:"apiUrl,omitempty" hcl:"url"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
Description *string `json:"description,omitempty" yaml:"description,omitempty" hcl:"description"`
AutoClose *bool `json:"autoClose,omitempty" yaml:"autoClose,omitempty" hcl:"auto_close"`
OverridePriority *bool `json:"overridePriority,omitempty" yaml:"overridePriority,omitempty" hcl:"override_priority"`
SendTagsAs *string `json:"sendTagsAs,omitempty" yaml:"sendTagsAs,omitempty" hcl:"send_tags_as"`
}
type PagerdutyIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
Key Secret `json:"integrationKey" yaml:"integrationKey" hcl:"integration_key"`
Severity *string `json:"severity,omitempty" yaml:"severity,omitempty" hcl:"severity"`
Class *string `json:"class,omitempty" yaml:"class,omitempty" hcl:"class"`
Component *string `json:"component,omitempty" yaml:"component,omitempty" hcl:"component"`
Group *string `json:"group,omitempty" yaml:"group,omitempty" hcl:"group"`
Summary *string `json:"summary,omitempty" yaml:"summary,omitempty" hcl:"summary"`
Source *string `json:"source,omitempty" yaml:"source,omitempty" hcl:"source"`
Client *string `json:"client,omitempty" yaml:"client,omitempty" hcl:"client"`
ClientURL *string `json:"client_url,omitempty" yaml:"client_url,omitempty" hcl:"client_url"`
Details *map[string]string `json:"details,omitempty" yaml:"details,omitempty" hcl:"details"`
}
type PushoverIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
UserKey Secret `json:"userKey" yaml:"userKey" hcl:"user_key"`
APIToken Secret `json:"apiToken" yaml:"apiToken" hcl:"api_token"`
AlertingPriority *int64 `json:"priority,omitempty" yaml:"priority,omitempty" hcl:"priority"`
OKPriority *int64 `json:"okPriority,omitempty" yaml:"okPriority,omitempty" hcl:"ok_priority"`
Retry *int64 `json:"retry,omitempty" yaml:"retry,omitempty" hcl:"retry"`
Expire *int64 `json:"expire,omitempty" yaml:"expire,omitempty" hcl:"expire"`
Device *string `json:"device,omitempty" yaml:"device,omitempty" hcl:"device"`
AlertingSound *string `json:"sound,omitempty" yaml:"sound,omitempty" hcl:"sound"`
OKSound *string `json:"okSound,omitempty" yaml:"okSound,omitempty" hcl:"ok_sound"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
UploadImage *bool `json:"uploadImage,omitempty" yaml:"uploadImage,omitempty" hcl:"upload_image"`
}
type SensugoIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL string `json:"url" yaml:"url" hcl:"url"`
APIKey Secret `json:"apikey" yaml:"apikey" hcl:"api_key"`
Entity *string `json:"entity,omitempty" yaml:"entity,omitempty" hcl:"entity"`
Check *string `json:"check,omitempty" yaml:"check,omitempty" hcl:"check"`
Namespace *string `json:"namespace,omitempty" yaml:"namespace,omitempty" hcl:"namespace"`
Handler *string `json:"handler,omitempty" yaml:"handler,omitempty" hcl:"handler"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
}
type SlackIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
EndpointURL *string `json:"endpointUrl,omitempty" yaml:"endpointUrl,omitempty" hcl:"endpoint_url"`
URL *Secret `json:"url,omitempty" yaml:"url,omitempty" hcl:"url"`
Token *Secret `json:"token,omitempty" yaml:"token,omitempty" hcl:"token"`
Recipient *string `json:"recipient,omitempty" yaml:"recipient,omitempty" hcl:"recipient"`
Text *string `json:"text,omitempty" yaml:"text,omitempty" hcl:"text"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Username *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"username"`
IconEmoji *string `json:"icon_emoji,omitempty" yaml:"icon_emoji,omitempty" hcl:"icon_emoji"`
IconURL *string `json:"icon_url,omitempty" yaml:"icon_url,omitempty" hcl:"icon_url"`
MentionChannel *string `json:"mentionChannel,omitempty" yaml:"mentionChannel,omitempty" hcl:"mention_channel"`
MentionUsers *string `json:"mentionUsers,omitempty" yaml:"mentionUsers,omitempty" hcl:"mention_users"`
MentionGroups *string `json:"mentionGroups,omitempty" yaml:"mentionGroups,omitempty" hcl:"mention_groups"`
}
type TelegramIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
BotToken Secret `json:"bottoken" yaml:"bottoken" hcl:"token"`
ChatID string `json:"chatid,omitempty" yaml:"chatid,omitempty" hcl:"chat_id"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
ParseMode *string `json:"parse_mode,omitempty" yaml:"parse_mode,omitempty" hcl:"parse_mode"`
DisableWebPagePreview *bool `json:"disable_web_page_preview,omitempty" yaml:"disable_web_page_preview,omitempty" hcl:"disable_web_page_preview"`
ProtectContent *bool `json:"protect_content,omitempty" yaml:"protect_content,omitempty" hcl:"protect_content"`
DisableNotifications *bool `json:"disable_notifications,omitempty" yaml:"disable_notifications,omitempty" hcl:"disable_notifications"`
}
type TeamsIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL Secret `json:"url,omitempty" yaml:"url,omitempty" hcl:"url"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
SectionTitle *string `json:"sectiontitle,omitempty" yaml:"sectiontitle,omitempty" hcl:"section_title"`
}
type ThreemaIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
GatewayID string `json:"gateway_id" yaml:"gateway_id" hcl:"gateway_id"`
RecipientID string `json:"recipient_id" yaml:"recipient_id" hcl:"recipient_id"`
APISecret Secret `json:"api_secret" yaml:"api_secret" hcl:"api_secret"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Description *string `json:"description,omitempty" yaml:"description,omitempty" hcl:"description"`
}
type VictoropsIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL string `json:"url" yaml:"url" hcl:"url"`
MessageType *string `json:"messageType,omitempty" yaml:"messageType,omitempty" hcl:"message_type"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Description *string `json:"description,omitempty" yaml:"description,omitempty" hcl:"description"`
}
type WebexIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
Token Secret `json:"bot_token" yaml:"bot_token" hcl:"token"`
APIURL *string `json:"api_url,omitempty" yaml:"api_url,omitempty" hcl:"api_url"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
RoomID *string `json:"room_id,omitempty" yaml:"room_id,omitempty" hcl:"room_id"`
}
type WebhookIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL string `json:"url" yaml:"url" hcl:"url"`
HTTPMethod *string `json:"httpMethod,omitempty" yaml:"httpMethod,omitempty" hcl:"http_method"`
MaxAlerts *int64 `json:"maxAlerts,omitempty" yaml:"maxAlerts,omitempty" hcl:"max_alerts"`
AuthorizationScheme *string `json:"authorization_scheme,omitempty" yaml:"authorization_scheme,omitempty" hcl:"authorization_scheme"`
AuthorizationCredentials *Secret `json:"authorization_credentials,omitempty" yaml:"authorization_credentials,omitempty" hcl:"authorization_credentials"`
User *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"basic_auth_user"`
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"basic_auth_password"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
}
type WecomIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
URL *Secret `json:"url,omitempty" yaml:"url,omitempty" hcl:"url"`
Secret *Secret `json:"secret,omitempty" yaml:"secret,omitempty" hcl:"secret"`
AgentID *string `json:"agent_id,omitempty" yaml:"agent_id,omitempty" hcl:"agent_id"`
CorpID *string `json:"corp_id,omitempty" yaml:"corp_id,omitempty" hcl:"corp_id"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
MsgType *string `json:"msgtype,omitempty" yaml:"msgtype,omitempty" hcl:"msg_type"`
ToUser *string `json:"touser,omitempty" yaml:"touser,omitempty" hcl:"to_user"`
}
type ContactPoint struct {
Name string `json:"name" yaml:"name" hcl:"name"`
Alertmanager []AlertmanagerIntegration `json:"alertmanager" yaml:"alertmanager" hcl:"alertmanager,block"`
Dingding []DingdingIntegration `json:"dingding" yaml:"dingding" hcl:"dingding,block"`
Discord []DiscordIntegration `json:"discord" yaml:"discord" hcl:"discord,block"`
Email []EmailIntegration `json:"email" yaml:"email" hcl:"email,block"`
Googlechat []GooglechatIntegration `json:"googlechat" yaml:"googlechat" hcl:"googlechat,block"`
Kafka []KafkaIntegration `json:"kafka" yaml:"kafka" hcl:"kafka,block"`
Line []LineIntegration `json:"line" yaml:"line" hcl:"line,block"`
Opsgenie []OpsgenieIntegration `json:"opsgenie" yaml:"opsgenie" hcl:"opsgenie,block"`
Pagerduty []PagerdutyIntegration `json:"pagerduty" yaml:"pagerduty" hcl:"pagerduty,block"`
OnCall []OnCallIntegration `json:"oncall" yaml:"oncall" hcl:"oncall,block"`
Pushover []PushoverIntegration `json:"pushover" yaml:"pushover" hcl:"pushover,block"`
Sensugo []SensugoIntegration `json:"sensugo" yaml:"sensugo" hcl:"sensugo,block"`
Slack []SlackIntegration `json:"slack" yaml:"slack" hcl:"slack,block"`
Teams []TeamsIntegration `json:"teams" yaml:"teams" hcl:"teams,block"`
Telegram []TelegramIntegration `json:"telegram" yaml:"telegram" hcl:"telegram,block"`
Threema []ThreemaIntegration `json:"threema" yaml:"threema" hcl:"threema,block"`
Victorops []VictoropsIntegration `json:"victorops" yaml:"victorops" hcl:"victorops,block"`
Webhook []WebhookIntegration `json:"webhook" yaml:"webhook" hcl:"webhook,block"`
Wecom []WecomIntegration `json:"wecom" yaml:"wecom" hcl:"wecom,block"`
Webex []WebexIntegration `json:"webex" yaml:"webex" hcl:"webex,block"`
}

View File

@ -6,7 +6,7 @@ import { alertRuleApi } from '../../api/alertRuleApi';
import { FileExportPreview } from './FileExportPreview';
import { GrafanaExportDrawer } from './GrafanaExportDrawer';
import { ExportFormats, jsonAndYamlGrafanaExportProviders } from './providers';
import { allGrafanaExportProviders, ExportFormats } from './providers';
interface GrafanaReceiverExportPreviewProps {
exportFormat: ExportFormats;
@ -57,7 +57,7 @@ export const GrafanaReceiverExporter = ({ onClose, receiverName, decrypt }: Graf
activeTab={activeTab}
onTabChange={setActiveTab}
onClose={onClose}
formatProviders={jsonAndYamlGrafanaExportProviders}
formatProviders={Object.values(allGrafanaExportProviders)}
>
<GrafanaReceiverExportPreview
receiverName={receiverName}

View File

@ -6,7 +6,7 @@ import { alertRuleApi } from '../../api/alertRuleApi';
import { FileExportPreview } from './FileExportPreview';
import { GrafanaExportDrawer } from './GrafanaExportDrawer';
import { ExportFormats, jsonAndYamlGrafanaExportProviders } from './providers';
import { allGrafanaExportProviders, ExportFormats } from './providers';
interface GrafanaReceiversExportPreviewProps {
exportFormat: ExportFormats;
@ -49,7 +49,7 @@ export const GrafanaReceiversExporter = ({ onClose, decrypt }: GrafanaReceiversE
activeTab={activeTab}
onTabChange={setActiveTab}
onClose={onClose}
formatProviders={jsonAndYamlGrafanaExportProviders}
formatProviders={Object.values(allGrafanaExportProviders)}
>
<GrafanaReceiversExportPreview decrypt={decrypt} exportFormat={activeTab} onClose={onClose} />
</GrafanaExportDrawer>