Skip to content

Commit 586ba3d

Browse files
Cherry pick 0.32.3 (#5366)
Signed-off-by: Ethan Hunter <ehunter@hudson-trading.com> Signed-off-by: Siavash Safi <siavash@cloudflare.com> Co-authored-by: Siavash Safi <siavash@cloudflare.com>
1 parent ce33c59 commit 586ba3d

6 files changed

Lines changed: 64 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@
44
* [FEATURE] ...
55
* [ENHANCEMENT] ...
66

7-
## 0.32.2 / 2026-05-25
7+
## 0.32.3 / 2026-07-04
8+
9+
* [BUGFIX] doc: fix missing `notification_reason` field in webhook documentation (#5329)
10+
* [BUGFIX] silences: fix silences snapshot missing legacy matchers field. This caused a bug that prevented older alertmanager versions from reading newer snapshots unnecessarily. (#5330)
11+
* [BUGFIX] silence with no matchers should populate an empty array in API response (#5331)
12+
13+
## 0.32.2 / 2026-06-05
814

915
* [BUGFIX] Fix dispatcher goroutine leaks on destroyed alertgroup swap. #5241
1016

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.32.2
1+
0.32.3

api/v2/compat.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func GettableSilenceFromProto(s *silencepb.Silence) (open_api_models.GettableSil
3838
Silence: open_api_models.Silence{
3939
StartsAt: &start,
4040
EndsAt: &end,
41+
Matchers: open_api_models.Matchers{},
4142
Comment: &s.Comment,
4243
CreatedBy: &s.CreatedBy,
4344
Annotations: s.Annotations,

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,6 +1895,7 @@ endpoint:
18951895
"commonLabels": <object>,
18961896
"commonAnnotations": <object>,
18971897
"externalURL": <string>, // backlink to the Alertmanager.
1898+
"notification_reason": <string>, // string represent the reason this notification was generated
18981899
"alerts": [
18991900
{
19001901
"status": "<resolved|firing>",

silence/silence.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,9 +1321,11 @@ func (s state) MarshalBinary() ([]byte, error) {
13211321
var buf bytes.Buffer
13221322

13231323
for _, e := range s {
1324-
if _, err := protodelim.MarshalTo(&buf, e); err != nil {
1324+
b, err := marshalMeshSilence(e)
1325+
if err != nil {
13251326
return nil, err
13261327
}
1328+
buf.Write(b)
13271329
}
13281330
return buf.Bytes(), nil
13291331
}
@@ -1354,7 +1356,9 @@ func decodeState(r io.Reader) (state, error) {
13541356
// the first matcher set to the matchers field for backward compatibility with
13551357
// older alertmanager versions.
13561358
func prepareSilenceForMarshalling(sil *pb.Silence) {
1357-
if len(sil.MatcherSets) > 0 {
1359+
// The nil check is here because of rare cases where this function
1360+
// is called on a nil silence. It's up to the caller to decide if it's a bug
1361+
if sil != nil && len(sil.MatcherSets) > 0 {
13581362
sil.Matchers = sil.MatcherSets[0].Matchers
13591363
}
13601364
}

silence/state_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@
1313
package silence
1414

1515
import (
16+
"bufio"
17+
"bytes"
1618
"testing"
1719
"time"
1820

1921
"github.com/stretchr/testify/require"
22+
"google.golang.org/protobuf/encoding/protodelim"
23+
"google.golang.org/protobuf/proto"
24+
"google.golang.org/protobuf/types/known/timestamppb"
25+
26+
pb "github.com/prometheus/alertmanager/silence/silencepb"
2027
)
2128

2229
func TestCurrentState(t *testing.T) {
@@ -37,3 +44,44 @@ func TestCurrentState(t *testing.T) {
3744
expected = CurrentState(pastStartTime, pastEndTime)
3845
require.Equal(t, SilenceStateExpired, expected)
3946
}
47+
48+
// TestStateMarshalBinaryPopulatesLegacyMatchers asserts that snapshots and
49+
// cluster local-state payloads written by state.MarshalBinary include the
50+
// deprecated Silence.Matchers field, so that older Alertmanagers that don't
51+
// understand MatcherSets still see the silence's first matcher set.
52+
func TestStateMarshalBinaryPopulatesLegacyMatchers(t *testing.T) {
53+
now := time.Now()
54+
matchers := []*pb.Matcher{
55+
{Name: "alertname", Pattern: "Foo", Type: pb.Matcher_EQUAL},
56+
{Name: "severity", Pattern: "warn|crit", Type: pb.Matcher_REGEXP},
57+
}
58+
sil := &pb.Silence{
59+
Id: "abc",
60+
MatcherSets: []*pb.MatcherSet{{Matchers: matchers}},
61+
StartsAt: timestamppb.New(now),
62+
EndsAt: timestamppb.New(now.Add(time.Hour)),
63+
}
64+
st := state{sil.Id: &pb.MeshSilence{
65+
Silence: sil,
66+
ExpiresAt: timestamppb.New(now.Add(2 * time.Hour)),
67+
}}
68+
69+
b, err := st.MarshalBinary()
70+
require.NoError(t, err)
71+
72+
require.Nil(t, sil.Matchers, "MarshalBinary must not mutate in-memory silences")
73+
74+
// Decode directly via protodelim, bypassing decodeState — decodeState
75+
// strips the legacy field, but we want to observe the on-the-wire shape.
76+
var got pb.MeshSilence
77+
require.NoError(t, protodelim.UnmarshalFrom(bufio.NewReader(bytes.NewReader(b)), &got))
78+
79+
require.Len(t, got.Silence.MatcherSets, 1)
80+
require.Len(t, got.Silence.Matchers, len(matchers))
81+
for i, m := range matchers {
82+
require.True(t, proto.Equal(m, got.Silence.Matchers[i]),
83+
"legacy Matchers[%d] mismatch", i)
84+
require.True(t, proto.Equal(m, got.Silence.MatcherSets[0].Matchers[i]),
85+
"MatcherSets[0].Matchers[%d] mismatch", i)
86+
}
87+
}

0 commit comments

Comments
 (0)