@@ -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"
@@ -209,6 +212,76 @@ func removeEmptyLabels(inputs []string) []string {
209212 return outputs
210213}
211214
215+ // eppExtraArgs are appended to the "epp" container's args in the Deployment
216+ // created by createEndPointPickerHelper.
217+ //
218+ // Graceful drain is disabled (--drain-timeout=0) for all e2e EPPs. The suites
219+ // create and delete the shared "e2e-epp" Deployment in sequence behind a single
220+ // Envoy; with a drain window a deleted EPP keeps serving ext_proc on its
221+ // existing connection, so Envoy lingers on the terminating pod (stale datastore)
222+ // and the next spec's requests fail. The graceful-drain behavior itself is
223+ // covered by unit tests.
224+ var eppExtraArgs = []string {"--drain-timeout=0" }
225+
226+ // appendEppArgs returns the input YAML docs with args appended to the "epp"
227+ // container of any Deployment. It is a no-op when args is empty.
228+ func appendEppArgs (inputs []string , args []string ) []string {
229+ if len (args ) == 0 {
230+ return inputs
231+ }
232+ outputs := make ([]string , len (inputs ))
233+ for idx , input := range inputs {
234+ docs := strings .Split (input , "\n ---" )
235+ rendered := make ([]string , 0 , len (docs ))
236+ for _ , doc := range docs {
237+ if strings .TrimSpace (doc ) == "" {
238+ continue
239+ }
240+ rendered = append (rendered , appendArgsToEppContainer (doc , args ))
241+ }
242+ outputs [idx ] = strings .Join (rendered , "\n ---\n " )
243+ }
244+ return outputs
245+ }
246+
247+ func appendArgsToEppContainer (doc string , args []string ) string {
248+ obj := & unstructured.Unstructured {}
249+ err := yaml .Unmarshal ([]byte (doc ), & obj .Object )
250+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
251+ if len (obj .Object ) == 0 || obj .GetKind () != kubernetesDeploymentKind {
252+ return doc
253+ }
254+ path := []string {"spec" , "template" , "spec" , "containers" }
255+ containers , found , err := unstructured .NestedSlice (obj .Object , path ... )
256+
257+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
258+ if ! found {
259+ return doc
260+ }
261+ for i , c := range containers {
262+ m , ok := c .(map [string ]any )
263+ if ! ok || m ["name" ] != "epp" {
264+ continue
265+ }
266+ existing , _ , err := unstructured .NestedStringSlice (m , "args" )
267+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
268+ merged := make ([]any , 0 , len (existing )+ len (args ))
269+ for _ , a := range existing {
270+ merged = append (merged , a )
271+ }
272+ for _ , a := range args {
273+ merged = append (merged , a )
274+ }
275+ m ["args" ] = merged
276+ containers [i ] = m
277+ }
278+ err = unstructured .SetNestedSlice (obj .Object , containers , path ... )
279+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
280+ out , err := yaml .Marshal (obj .Object )
281+ gomega .Expect (err ).ShouldNot (gomega .HaveOccurred ())
282+ return strings .TrimRight (string (out ), "\n " )
283+ }
284+
212285func substituteMany (inputs []string , substitutions map [string ]string ) []string {
213286 outputs := make ([]string , len (inputs ))
214287 for idx , input := range inputs {
0 commit comments