fix(alertmanager): disallow creating invalid channels (#9946)

This commit is contained in:
Pandey
2026-01-08 00:13:46 +05:30
committed by GitHub
parent fce1cce02e
commit 23ba9dacd1
3 changed files with 83 additions and 8 deletions

View File

@@ -2,9 +2,10 @@ package signozalertmanager
import (
"context"
"time"
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
"github.com/prometheus/common/model"
"time"
amConfig "github.com/prometheus/alertmanager/config"
@@ -191,7 +192,11 @@ func (provider *provider) CreateChannel(ctx context.Context, orgID string, recei
return err
}
channel := alertmanagertypes.NewChannelFromReceiver(receiver, orgID)
channel, err := alertmanagertypes.NewChannelFromReceiver(receiver, orgID)
if err != nil {
return err
}
return provider.configStore.CreateChannel(ctx, channel, alertmanagertypes.WithCb(func(ctx context.Context) error {
return provider.configStore.Set(ctx, config)
}))

View File

@@ -16,6 +16,7 @@ import (
var (
ErrCodeAlertmanagerChannelNotFound = errors.MustNewCode("alertmanager_channel_not_found")
ErrCodeAlertmanagerChannelNameMismatch = errors.MustNewCode("alertmanager_channel_name_mismatch")
ErrCodeAlertmanagerChannelInvalid = errors.MustNewCode("alertmanager_channel_invalid")
)
var (
@@ -41,9 +42,9 @@ type Channel struct {
// NewChannelFromReceiver creates a new Channel from a Receiver.
// It can return nil if the receiver is the default receiver.
func NewChannelFromReceiver(receiver config.Receiver, orgID string) *Channel {
func NewChannelFromReceiver(receiver config.Receiver, orgID string) (*Channel, error) {
if receiver.Name == DefaultReceiverName {
return nil
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAlertmanagerChannelInvalid, "cannot use %s name as a channel name", receiver.Name)
}
// Initialize channel with common fields
@@ -98,7 +99,12 @@ func NewChannelFromReceiver(receiver config.Receiver, orgID string) *Channel {
break
}
return &channel
// If we were unable to find the channel type, return an error
if channel.Type == "" {
return nil, errors.Newf(errors.TypeInvalidInput, ErrCodeAlertmanagerChannelInvalid, "channel '%s' must have at least one notification configuration (e.g., email_configs, webhook_configs, slack_configs)", receiver.Name)
}
return &channel, nil
}
func NewConfigFromChannels(globalConfig GlobalConfig, routeConfig RouteConfig, channels Channels, orgID string) (*Config, error) {
@@ -163,9 +169,9 @@ func NewStatsFromChannels(channels Channels) map[string]any {
}
func (c *Channel) Update(receiver Receiver) error {
channel := NewChannelFromReceiver(receiver, c.OrgID)
if channel == nil {
return errors.Newf(errors.TypeInvalidInput, ErrCodeAlertmanagerChannelNotFound, "cannot find channel with id %s", c.ID.StringValue())
channel, err := NewChannelFromReceiver(receiver, c.OrgID)
if err != nil {
return err
}
if c.Name != channel.Name {

View File

@@ -2,6 +2,7 @@ package alertmanagertypes
import (
"encoding/json"
"net/url"
"testing"
"time"
@@ -228,3 +229,66 @@ func TestNewConfigFromChannels(t *testing.T) {
})
}
}
func TestNewChannelFromReceiver(t *testing.T) {
testCases := []struct {
name string
receiver config.Receiver
expected *Channel
pass bool
}{
{
name: "InvalidReceiver_OnlyName",
receiver: config.Receiver{
Name: "test-receiver",
},
expected: nil,
pass: false,
},
{
name: "InvalidReceiver_DefaultReceiver",
receiver: config.Receiver{
Name: DefaultReceiverName,
},
expected: nil,
pass: false,
},
{
name: "ValidReceiver_Slack",
receiver: config.Receiver{
Name: "test-receiver",
SlackConfigs: []*config.SlackConfig{
{
Channel: "#alerts",
APIURL: &config.SecretURL{URL: &url.URL{Scheme: "https", Host: "slack.com", Path: "/api/test"}},
NotifierConfig: config.NotifierConfig{
VSendResolved: true,
},
},
},
},
expected: &Channel{
Name: "test-receiver",
Type: "slack",
Data: `{"name":"test-receiver","slack_configs":[{"send_resolved":true,"api_url":"https://slack.com/api/test","channel":"#alerts"}]}`,
},
pass: true,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
channel, err := NewChannelFromReceiver(testCase.receiver, "1")
if !testCase.pass {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, testCase.expected.Name, channel.Name)
assert.Equal(t, testCase.expected.Type, channel.Type)
assert.Equal(t, testCase.expected.Data, channel.Data)
})
}
}