@@ -14,9 +14,12 @@ import (
1414 "github.com/onsi/ginkgo/v2"
1515 "github.com/onsi/gomega"
1616 "github.com/onsi/gomega/gexec"
17+ "sigs.k8s.io/yaml"
18+
1719 appsv1 "k8s.io/api/apps/v1"
1820 corev1 "k8s.io/api/core/v1"
1921 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2023 apilabels "k8s.io/apimachinery/pkg/labels"
2124 "k8s.io/apimachinery/pkg/types"
2225 "sigs.k8s.io/controller-runtime/pkg/client"
@@ -191,6 +194,76 @@ func removeEmptyLabels(inputs []string) []string {
191194 return outputs
192195}
193196
197+ // eppExtraArgs are appended to the "epp" container's args in the Deployment
198+ // created by createEndPointPickerHelper.
199+ //
200+ // Graceful drain is disabled (--drain-timeout=0) for all e2e EPPs. The suites
201+ // create and delete the shared "e2e-epp" Deployment in sequence behind a single
202+ // Envoy; with a drain window a deleted EPP keeps serving ext_proc on its
203+ // existing connection, so Envoy lingers on the terminating pod (stale datastore)
204+ // and the next spec's requests fail. The graceful-drain behavior itself is
205+ // covered by unit tests.
206+ var eppExtraArgs = []string {"--drain-timeout=0" }
207+
208+ // appendEppArgs returns the input YAML docs with args appended to the "epp"
209+ // container of any Deployment. It is a no-op when args is empty.
210+ func appendEppArgs (inputs []string , args []string ) []string {
211+ if len (args ) == 0 {
212+ return inputs
213+ }
214+ outputs := make ([]string , len (inputs ))
215+ for idx , input := range inputs {
216+ docs := strings .Split (input , "\n ---" )
217+ rendered := make ([]string , 0 , len (docs ))
218+ for _ , doc := range docs {
219+ if strings .TrimSpace (doc ) == "" {
220+ continue
221+ }
222+ rendered = append (rendered , appendArgsToEppContainer (doc , args ))
223+ }
224+ outputs [idx ] = strings .Join (rendered , "\n ---\n " )
225+ }
226+ return outputs
227+ }
228+
229+ func appendArgsToEppContainer (doc string , args []string ) string {
230+ obj := & unstructured.Unstructured {}
231+ err := yaml .Unmarshal ([]byte (doc ), & obj .Object )
232+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
233+ if len (obj .Object ) == 0 || obj .GetKind () != kubernetesDeploymentKind {
234+ return doc
235+ }
236+ path := []string {"spec" , "template" , "spec" , "containers" }
237+ containers , found , err := unstructured .NestedSlice (obj .Object , path ... )
238+
239+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
240+ if ! found {
241+ return doc
242+ }
243+ for i , c := range containers {
244+ m , ok := c .(map [string ]any )
245+ if ! ok || m ["name" ] != "epp" {
246+ continue
247+ }
248+ existing , _ , err := unstructured .NestedStringSlice (m , "args" )
249+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
250+ merged := make ([]any , 0 , len (existing )+ len (args ))
251+ for _ , a := range existing {
252+ merged = append (merged , a )
253+ }
254+ for _ , a := range args {
255+ merged = append (merged , a )
256+ }
257+ m ["args" ] = merged
258+ containers [i ] = m
259+ }
260+ err = unstructured .SetNestedSlice (obj .Object , containers , path ... )
261+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
262+ out , err := yaml .Marshal (obj .Object )
263+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
264+ return strings .TrimRight (string (out ), "\n " )
265+ }
266+
194267func substituteMany (inputs []string , substitutions map [string ]string ) []string {
195268 outputs := make ([]string , len (inputs ))
196269 for idx , input := range inputs {
0 commit comments