Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/v2/compat.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func GettableSilenceFromProto(s *silencepb.Silence) (open_api_models.GettableSil
Silence: open_api_models.Silence{
StartsAt: &start,
EndsAt: &end,
Matchers: open_api_models.Matchers{},
Comment: &s.Comment,
CreatedBy: &s.CreatedBy,
Annotations: s.Annotations,
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1895,6 +1895,7 @@ endpoint:
"commonLabels": <object>,
"commonAnnotations": <object>,
"externalURL": <string>, // backlink to the Alertmanager.
"notification_reason": <string>, // string represent the reason this notification was generated
"alerts": [
{
"status": "<resolved|firing>",
Expand Down
8 changes: 6 additions & 2 deletions silence/silence.go
Original file line number Diff line number Diff line change
Expand Up @@ -1321,9 +1321,11 @@ func (s state) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer

for _, e := range s {
if _, err := protodelim.MarshalTo(&buf, e); err != nil {
b, err := marshalMeshSilence(e)
if err != nil {
return nil, err
}
buf.Write(b)
}
return buf.Bytes(), nil
}
Expand Down Expand Up @@ -1354,7 +1356,9 @@ func decodeState(r io.Reader) (state, error) {
// the first matcher set to the matchers field for backward compatibility with
// older alertmanager versions.
func prepareSilenceForMarshalling(sil *pb.Silence) {
if len(sil.MatcherSets) > 0 {
// The nil check is here because of rare cases where this function
// is called on a nil silence. It's up to the caller to decide if it's a bug
if sil != nil && len(sil.MatcherSets) > 0 {
sil.Matchers = sil.MatcherSets[0].Matchers
}
}
Expand Down
48 changes: 48 additions & 0 deletions silence/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@
package silence

import (
"bufio"
"bytes"
"testing"
"time"

"github.com/stretchr/testify/require"
"google.golang.org/protobuf/encoding/protodelim"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"

pb "github.com/prometheus/alertmanager/silence/silencepb"
)

func TestCurrentState(t *testing.T) {
Expand All @@ -37,3 +44,44 @@ func TestCurrentState(t *testing.T) {
expected = CurrentState(pastStartTime, pastEndTime)
require.Equal(t, SilenceStateExpired, expected)
}

// TestStateMarshalBinaryPopulatesLegacyMatchers asserts that snapshots and
// cluster local-state payloads written by state.MarshalBinary include the
// deprecated Silence.Matchers field, so that older Alertmanagers that don't
// understand MatcherSets still see the silence's first matcher set.
func TestStateMarshalBinaryPopulatesLegacyMatchers(t *testing.T) {
now := time.Now()
matchers := []*pb.Matcher{
{Name: "alertname", Pattern: "Foo", Type: pb.Matcher_EQUAL},
{Name: "severity", Pattern: "warn|crit", Type: pb.Matcher_REGEXP},
}
sil := &pb.Silence{
Id: "abc",
MatcherSets: []*pb.MatcherSet{{Matchers: matchers}},
StartsAt: timestamppb.New(now),
EndsAt: timestamppb.New(now.Add(time.Hour)),
}
st := state{sil.Id: &pb.MeshSilence{
Silence: sil,
ExpiresAt: timestamppb.New(now.Add(2 * time.Hour)),
}}

b, err := st.MarshalBinary()
require.NoError(t, err)

require.Nil(t, sil.Matchers, "MarshalBinary must not mutate in-memory silences")

// Decode directly via protodelim, bypassing decodeState — decodeState
// strips the legacy field, but we want to observe the on-the-wire shape.
var got pb.MeshSilence
require.NoError(t, protodelim.UnmarshalFrom(bufio.NewReader(bytes.NewReader(b)), &got))

require.Len(t, got.Silence.MatcherSets, 1)
require.Len(t, got.Silence.Matchers, len(matchers))
for i, m := range matchers {
require.True(t, proto.Equal(m, got.Silence.Matchers[i]),
"legacy Matchers[%d] mismatch", i)
require.True(t, proto.Equal(m, got.Silence.MatcherSets[0].Matchers[i]),
"MatcherSets[0].Matchers[%d] mismatch", i)
}
}
Loading