probes.go RawHistoryFolder

1package deployment
2
3import (
4	"bufio"
5	"context"
6	"errors"
7	"fmt"
8	"io"
9	"net"
10	"net/http"
11	"strings"
12	"time"
13)
14
15// probeHTTPClient is the dedicated readiness client. It never consults
16// environment proxy settings, never follows redirects, and bounds every
17// request so a nonresponding workload cannot hold the probe open.
18var probeHTTPClient = &http.Client{
19	Transport: &http.Transport{Proxy: nil},
20	CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
21		return http.ErrUseLastResponse
22	},
23	Timeout: 5 * time.Second,
24}
25
26// httpGetOnce performs one HTTP GET without following redirects. It dials
27// probeURL's authority (the loopback published bind) while sending
28// hostAuthority as the Host header, so a workload that validates the Host
29// port of its declared container port accepts the probe.
30func httpGetOnce(ctx context.Context, probeURL, hostAuthority string) (int, error) {
31	req, err := http.NewRequestWithContext(ctx, http.MethodGet, probeURL, nil)
32	if err != nil {
33		return 0, err
34	}
35	if hostAuthority != "" {
36		req.Host = hostAuthority
37	}
38	resp, err := probeHTTPClient.Do(req)
39	if err != nil {
40		return 0, err
41	}
42	defer func() { _ = resp.Body.Close() }()
43	return resp.StatusCode, nil
44}
45
46// tcpSettleWindow is how long an accepted TCP probe connection must stay open
47// (or deliver data) before the target counts as ready.
48const tcpSettleWindow = 300 * time.Millisecond
49
50// errTCPProbeClosed reports a connection that was accepted and then closed
51// within the settle window.
52var errTCPProbeClosed = errors.New("connection closed right after accept")
53
54// tcpDialOnce attempts one TCP readiness check against a published loopback
55// bind. A successful connect alone proves nothing: the host-side port relay
56// (rootless Docker, docker-proxy) accepts on behalf of the container and then
57// closes at once while the workload is not listening yet. So the connection
58// must also survive tcpSettleWindow: data from the workload (a banner) or a
59// connection still open at the end of the window means ready; EOF or a reset
60// means not ready.
61func tcpDialOnce(ctx context.Context, addr string) error {
62	return tcpProbeOnce(ctx, addr, tcpSettleWindow)
63}
64
65func tcpProbeOnce(ctx context.Context, addr string, settle time.Duration) error {
66	dialer := &net.Dialer{Timeout: 2 * time.Second}
67	conn, err := dialer.DialContext(ctx, "tcp", addr)
68	if err != nil {
69		return err
70	}
71	defer func() { _ = conn.Close() }()
72	if err := conn.SetReadDeadline(time.Now().Add(settle)); err != nil {
73		return fmt.Errorf("set probe read deadline: %w", err)
74	}
75	stop := context.AfterFunc(ctx, func() { _ = conn.SetReadDeadline(time.Now()) })
76	defer stop()
77	var buf [1]byte
78	n, err := conn.Read(buf[:])
79	if ctxErr := ctx.Err(); ctxErr != nil {
80		return ctxErr
81	}
82	if n > 0 {
83		return nil
84	}
85	var netErr net.Error
86	if errors.As(err, &netErr) && netErr.Timeout() {
87		return nil
88	}
89	if err == nil || errors.Is(err, io.EOF) {
90		return errTCPProbeClosed
91	}
92	return err
93}
94
95// scanForMarker reports whether the stream contains the marker.
96func scanForMarker(stream io.ReadCloser, marker string) (bool, error) {
97	scanner := bufio.NewScanner(stream)
98	scanner.Buffer(make([]byte, 64*1024), 1024*1024)
99	for scanner.Scan() {
100		if strings.Contains(scanner.Text(), marker) {
101			return true, nil
102		}
103	}
104	if err := scanner.Err(); err != nil {
105		return false, fmt.Errorf("deployment: read logs: %w", err)
106	}
107	return false, nil
108}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close