@@ -149,6 +149,58 @@ func curlHTTPCodeFromClient(ctx context.Context, host, url string) (code string,
149149 return strings .TrimSpace (out ), out , nil
150150}
151151
152+ // curlHTTPRedirectFromClient executes curl and returns the HTTP status code and Location header.
153+ // It does NOT follow redirects (no -L flag) so we can see the redirect response.
154+ func curlHTTPRedirectFromClient (ctx context.Context , host , url string ) (code string , location string , out string , err error ) {
155+ // Use -i to include headers, -s for silent, but keep stderr for errors
156+ // Extract status code and Location header
157+ script := `set -o pipefail; curl -sSi --connect-timeout 2 --max-time 5 -H "Host: $1" "$2" 2>&1 || echo "000"`
158+ out , err = kubectl (ctx , "-n" , "default" , "exec" , "deploy/curl" , "--" ,
159+ "sh" , "-c" , script , "_" , host , url ,
160+ )
161+ if err != nil {
162+ return "000" , "" , out , err
163+ }
164+
165+ // Parse status code from HTTP/1.1 308 Permanent Redirect
166+ lines := strings .Split (out , "\n " )
167+ code = "000"
168+ location = ""
169+ for _ , line := range lines {
170+ lineLower := strings .ToLower (line )
171+ if strings .HasPrefix (line , "HTTP/" ) {
172+ // Extract status code from "HTTP/1.1 308 Permanent Redirect"
173+ parts := strings .Fields (line )
174+ if len (parts ) >= 2 {
175+ code = parts [1 ]
176+ }
177+ }
178+ if strings .HasPrefix (lineLower , "location:" ) {
179+ // Extract Location header value (case-insensitive)
180+ // Handle both "Location: https://..." and "location: https://..."
181+ idx := strings .Index (lineLower , "location:" )
182+ if idx != - 1 {
183+ location = strings .TrimSpace (line [idx + 9 :])
184+ }
185+ }
186+ }
187+
188+ return code , location , out , nil
189+ }
190+
191+ // curlHTTPS200FromClient executes curl with -k (insecure) flag for HTTPS requests.
192+ func curlHTTPS200FromClient (ctx context.Context , host , url string ) (code string , out string , err error ) {
193+ // Use -k to skip certificate verification for self-signed certs
194+ script := `set -o pipefail; curl -k -sS -o /dev/null -w "%{http_code}" --connect-timeout 2 --max-time 5 -H "Host: $1" "$2" || echo 000`
195+ out , err = kubectl (ctx , "-n" , "default" , "exec" , "deploy/curl" , "--" ,
196+ "sh" , "-c" , script , "_" , host , url ,
197+ )
198+ if err != nil {
199+ return "000" , out , err
200+ }
201+ return strings .TrimSpace (out ), out , nil
202+ }
203+
152204func debugCurlVerbose (t * testing.T , ctx context.Context , host , url string ) error {
153205 t .Helper ()
154206 script := `curl -v --connect-timeout 2 --max-time 5 -H "Host: $1" "$2" || true`
@@ -159,6 +211,80 @@ func debugCurlVerbose(t *testing.T, ctx context.Context, host, url string) error
159211 return err
160212}
161213
214+ // requireHTTPRedirectEventually waits for an HTTP redirect response (308 status code)
215+ // and verifies the Location header contains https:// scheme.
216+ func requireHTTPRedirectEventually (t * testing.T , ctx context.Context , host , url string , timeout time.Duration ) {
217+ t .Helper ()
218+
219+ deadline := time .Now ().Add (timeout )
220+ interval := 2 * time .Second
221+
222+ var lastCode string
223+ var lastLocation string
224+ var lastOut string
225+ var lastErr error
226+
227+ for attempt := 1 ; time .Now ().Before (deadline ); attempt ++ {
228+ code , location , out , err := curlHTTPRedirectFromClient (ctx , host , url )
229+ lastCode , lastLocation , lastOut , lastErr = code , location , out , err
230+
231+ if err == nil && strings .TrimSpace (code ) == "308" {
232+ // Verify Location header contains https://
233+ if strings .HasPrefix (strings .ToLower (location ), "https://" ) {
234+ return
235+ }
236+ }
237+
238+ if attempt == 1 || attempt % 10 == 0 {
239+ t .Logf ("waiting for HTTP 308 redirect (attempt=%d host=%s url=%s): code=%q location=%q err=%v" ,
240+ attempt , host , url , strings .TrimSpace (code ), location , err )
241+ }
242+ time .Sleep (interval )
243+ }
244+
245+ _ = debugCurlVerbose (t , ctx , host , url )
246+
247+ t .Fatalf ("timed out waiting for HTTP 308 redirect (host=%s url=%s timeout=%s). lastCode=%q lastLocation=%q lastErr=%v lastOut=%s" ,
248+ host , url , timeout , strings .TrimSpace (lastCode ), lastLocation , lastErr , lastOut )
249+ }
250+
251+ // requireHTTPS200Eventually waits for an HTTPS 200 response using insecure curl (-k flag).
252+ func requireHTTPS200Eventually (t * testing.T , ctx context.Context , host , url string , timeout time.Duration ) {
253+ t .Helper ()
254+
255+ deadline := time .Now ().Add (timeout )
256+ interval := 2 * time .Second
257+
258+ var lastCode string
259+ var lastOut string
260+ var lastErr error
261+
262+ for attempt := 1 ; time .Now ().Before (deadline ); attempt ++ {
263+ code , out , err := curlHTTPS200FromClient (ctx , host , url )
264+ lastCode , lastOut , lastErr = code , out , err
265+
266+ if err == nil && strings .TrimSpace (code ) == "200" {
267+ return
268+ }
269+
270+ if attempt == 1 || attempt % 10 == 0 {
271+ t .Logf ("waiting for HTTPS 200 (attempt=%d host=%s url=%s): code=%q err=%v" ,
272+ attempt , host , url , strings .TrimSpace (code ), err )
273+ }
274+ time .Sleep (interval )
275+ }
276+
277+ // Debug with verbose curl
278+ script := `curl -kv --connect-timeout 2 --max-time 5 -H "Host: $1" "$2" || true`
279+ out , _ := kubectl (ctx , "-n" , "default" , "exec" , "deploy/curl" , "--" ,
280+ "sh" , "-c" , script , "_" , host , url ,
281+ )
282+ t .Logf ("debug curl -kv output:\n %s" , out )
283+
284+ t .Fatalf ("timed out waiting for HTTPS 200 (host=%s url=%s timeout=%s). lastCode=%q lastErr=%v lastOut=%s" ,
285+ host , url , timeout , strings .TrimSpace (lastCode ), lastErr , lastOut )
286+ }
287+
162288func waitForGatewayAddress (ctx context.Context , ns , gwName string , timeout time.Duration ) (string , error ) {
163289 deadline := time .Now ().Add (timeout )
164290 for time .Now ().Before (deadline ) {
0 commit comments