Skip to content

Commit 74083df

Browse files
authored
fix: truncate status condition messages to 32768 (#7159)
* fix: truncate status condition messages to 32768 Fixes: #7146 Signed-off-by: Arko Dasgupta <[email protected]>
1 parent 4093d0c commit 74083df

File tree

5 files changed

+81
-13
lines changed

5 files changed

+81
-13
lines changed

internal/gatewayapi/status/conditions.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,27 @@ import (
2222
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2323
)
2424

25+
const (
26+
conditionMessageMaxLength = 32768
27+
conditionMessageTruncationSuffix = " (message truncated)."
28+
)
29+
2530
// MergeConditions adds or updates matching conditions, and updates the transition
2631
// time if details of a condition have changed. Returns the updated condition array.
2732
func MergeConditions(conditions []metav1.Condition, updates ...metav1.Condition) []metav1.Condition {
2833
var additions []metav1.Condition
29-
for i, update := range updates {
34+
for i := range updates {
35+
updates[i].Message = truncateConditionMessage(updates[i].Message)
3036
add := true
31-
for j, cond := range conditions {
32-
if cond.Type == update.Type {
37+
for j := range conditions {
38+
if conditions[j].Type == updates[i].Type {
3339
add = false
34-
if conditionChanged(&cond, &update) {
35-
conditions[j].Status = update.Status
36-
conditions[j].Reason = update.Reason
37-
conditions[j].Message = update.Message
38-
conditions[j].ObservedGeneration = update.ObservedGeneration
39-
conditions[j].LastTransitionTime = update.LastTransitionTime
40+
if conditionChanged(&conditions[j], &updates[i]) {
41+
conditions[j].Status = updates[i].Status
42+
conditions[j].Reason = updates[i].Reason
43+
conditions[j].Message = updates[i].Message
44+
conditions[j].ObservedGeneration = updates[i].ObservedGeneration
45+
conditions[j].LastTransitionTime = updates[i].LastTransitionTime
4046
break
4147
}
4248
}
@@ -54,7 +60,7 @@ func newCondition(t string, status metav1.ConditionStatus, reason, msg string, l
5460
Type: t,
5561
Status: status,
5662
Reason: reason,
57-
Message: msg,
63+
Message: truncateConditionMessage(msg),
5864
LastTransitionTime: metav1.NewTime(lt),
5965
ObservedGeneration: og,
6066
}
@@ -95,3 +101,11 @@ func Error2ConditionMsg(err error) string {
95101
// Convert the rune slice back to a string
96102
return string(runes)
97103
}
104+
105+
func truncateConditionMessage(msg string) string {
106+
if len(msg) <= conditionMessageMaxLength {
107+
return msg
108+
}
109+
suffixLen := len(conditionMessageTruncationSuffix)
110+
return msg[:conditionMessageMaxLength-suffixLen] + conditionMessageTruncationSuffix
111+
}

internal/gatewayapi/status/conditions_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package status
1515

1616
import (
1717
"errors"
18+
"strings"
1819
"testing"
1920
"time"
2021

@@ -185,6 +186,20 @@ func TestMergeConditions(t *testing.T) {
185186
}
186187
}
187188

189+
func TestMergeConditionsTruncatesMessages(t *testing.T) {
190+
longMsg := strings.Repeat("x", conditionMessageMaxLength+5)
191+
cond := newCondition("available", metav1.ConditionTrue, "Reason", longMsg, time.Now(), 1)
192+
conditions := MergeConditions(nil, cond)
193+
194+
if assert.Len(t, conditions, 1) {
195+
assert.Len(t, conditions[0].Message, conditionMessageMaxLength)
196+
prefixLen := conditionMessageMaxLength - len(conditionMessageTruncationSuffix)
197+
expectedPrefix := strings.Repeat("x", prefixLen)
198+
assert.True(t, strings.HasSuffix(conditions[0].Message, conditionMessageTruncationSuffix))
199+
assert.Equal(t, expectedPrefix, conditions[0].Message[:prefixLen])
200+
}
201+
}
202+
188203
func TestError2ConditionMsg(t *testing.T) {
189204
testCases := []struct {
190205
name string

internal/gatewayapi/status/policy.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ func SetConditionForPolicyAncestor(policyStatus *gwapiv1a2.PolicyStatus, ancesto
8686
policyStatus.Ancestors = []gwapiv1a2.PolicyAncestorStatus{}
8787
}
8888

89+
sanitizedMessage := truncateConditionMessage(message)
90+
8991
// Find existing ancestor first
9092
for i, ancestor := range policyStatus.Ancestors {
9193
if string(ancestor.ControllerName) == controllerName && ancestorRefsEqual(&ancestor.AncestorRef, ancestorRef) {
@@ -94,21 +96,21 @@ func SetConditionForPolicyAncestor(policyStatus *gwapiv1a2.PolicyStatus, ancesto
9496
if existingCond.Type == string(conditionType) &&
9597
existingCond.Status == status &&
9698
existingCond.Reason == string(reason) &&
97-
existingCond.Message == message &&
99+
existingCond.Message == sanitizedMessage &&
98100
existingCond.ObservedGeneration == generation {
99101
return
100102
}
101103
}
102104

103105
// Only create condition and merge if needed
104-
cond := newCondition(string(conditionType), status, string(reason), message, time.Now(), generation)
106+
cond := newCondition(string(conditionType), status, string(reason), sanitizedMessage, time.Now(), generation)
105107
policyStatus.Ancestors[i].Conditions = MergeConditions(policyStatus.Ancestors[i].Conditions, cond)
106108
return
107109
}
108110
}
109111

110112
// Add condition for new PolicyAncestorStatus
111-
cond := newCondition(string(conditionType), status, string(reason), message, time.Now(), generation)
113+
cond := newCondition(string(conditionType), status, string(reason), sanitizedMessage, time.Now(), generation)
112114
policyStatus.Ancestors = append(policyStatus.Ancestors, gwapiv1a2.PolicyAncestorStatus{
113115
AncestorRef: *ancestorRef,
114116
ControllerName: gwapiv1a2.GatewayController(controllerName),
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright Envoy Gateway Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
// The full text of the Apache license is available in the LICENSE file at
4+
// the root of the repo.
5+
6+
package status
7+
8+
import (
9+
"strings"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
15+
)
16+
17+
func TestSetConditionForPolicyAncestorsTruncatesMessages(t *testing.T) {
18+
longMsg := strings.Repeat("x", conditionMessageMaxLength+5)
19+
policyStatus := &gwapiv1a2.PolicyStatus{}
20+
ancestorRef := &gwapiv1a2.ParentReference{Name: gwapiv1a2.ObjectName("example")}
21+
22+
SetConditionForPolicyAncestors(policyStatus, []*gwapiv1a2.ParentReference{ancestorRef}, "example.com/controller",
23+
gwapiv1a2.PolicyConditionAccepted, metav1.ConditionTrue, gwapiv1a2.PolicyReasonAccepted, longMsg, 1)
24+
25+
if assert.Len(t, policyStatus.Ancestors, 1) {
26+
ancestor := policyStatus.Ancestors[0]
27+
if assert.Len(t, ancestor.Conditions, 1) {
28+
gotMsg := ancestor.Conditions[0].Message
29+
assert.Len(t, gotMsg, conditionMessageMaxLength)
30+
prefixLen := conditionMessageMaxLength - len(conditionMessageTruncationSuffix)
31+
expectedPrefix := strings.Repeat("x", prefixLen)
32+
assert.True(t, strings.HasSuffix(gotMsg, conditionMessageTruncationSuffix))
33+
assert.Equal(t, expectedPrefix, gotMsg[:prefixLen])
34+
}
35+
}
36+
}

release-notes/current.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ bug fixes: |
3232
Fixed service account token handling in GatewayNamespaceMode to use SDS for properly refreshing expired token.
3333
Fixed handling of regex meta characters in prefix match replace for URL rewrite.
3434
Disabled the default emission of `x-envoy-ratelimited` headers from the rate limit filter; re-enable with the `enableEnvoyHeaders` setting in ClientTrafficPolicy.
35+
Truncated Gateway API status condition messages to stay within Kubernetes limits and prevent update failures.
3536
3637
# Enhancements that improve performance.
3738
performance improvements: |

0 commit comments

Comments
 (0)