1package deployment
2
3import (
4 "bufio"
5 "context"
6 "fmt"
7 "io"
8 "net"
9 "net/http"
10 "strings"
11 "time"
12)
13
14// probeHTTPClient is the dedicated readiness client. It never consults
15// environment proxy settings, never follows redirects, and bounds every
16// request so a nonresponding workload cannot hold the probe open.
17var probeHTTPClient = &http.Client{
18 Transport: &http.Transport{Proxy: nil},
19 CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
20 return http.ErrUseLastResponse
21 },
22 Timeout: 5 * time.Second,
23}
24
25// httpGetOnce performs one HTTP GET without following redirects. It dials
26// probeURL's authority (the loopback published bind) while sending
27// hostAuthority as the Host header, so a workload that validates the Host
28// port of its declared container port accepts the probe.
29func httpGetOnce(ctx context.Context, probeURL, hostAuthority string) (int, error) {
30 req, err := http.NewRequestWithContext(ctx, http.MethodGet, probeURL, nil)
31 if err != nil {
32 return 0, err
33 }
34 if hostAuthority != "" {
35 req.Host = hostAuthority
36 }
37 resp, err := probeHTTPClient.Do(req)
38 if err != nil {
39 return 0, err
40 }
41 defer func() { _ = resp.Body.Close() }()
42 return resp.StatusCode, nil
43}
44
45// tcpDialOnce attempts one TCP connection.
46func tcpDialOnce(ctx context.Context, addr string) error {
47 dialer := &net.Dialer{Timeout: 2 * time.Second}
48 conn, err := dialer.DialContext(ctx, "tcp", addr)
49 if err != nil {
50 return err
51 }
52 _ = conn.Close()
53 return nil
54}
55
56// scanForMarker reports whether the stream contains the marker.
57func scanForMarker(stream io.ReadCloser, marker string) (bool, error) {
58 scanner := bufio.NewScanner(stream)
59 scanner.Buffer(make([]byte, 64*1024), 1024*1024)
60 for scanner.Scan() {
61 if strings.Contains(scanner.Text(), marker) {
62 return true, nil
63 }
64 }
65 if err := scanner.Err(); err != nil {
66 return false, fmt.Errorf("deployment: read logs: %w", err)
67 }
68 return false, nil
69}