fix(worker): harden control-plane lifecycle
Все проверки выполнены успешно
CI / test (push) Successful in 2m32s
Docker / Build and publish worker image (push) Successful in 18m17s
Все проверки выполнены успешно
CI / test (push) Successful in 2m32s
Docker / Build and publish worker image (push) Successful in 18m17s
- reconnect safely after token rotation and retry leased results - reject malformed tasks and remove production cluster debug mutation - validate environment files and require immutable container images BREAKING CHANGE: Docker install, deploy, and Compose now require an immutable repository@sha256 image reference.
Этот коммит содержится в:
@@ -2,12 +2,15 @@ package distworker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -49,12 +52,16 @@ func NewClient(endpoint, authToken string) *Client {
|
||||
|
||||
// postJSON is a helper for sending JSON POST requests
|
||||
func (c *Client) postJSON(path string, payload interface{}) (*http.Response, error) {
|
||||
return c.postJSONContext(context.Background(), path, payload)
|
||||
}
|
||||
|
||||
func (c *Client) postJSONContext(ctx context.Context, path string, payload interface{}) (*http.Response, error) {
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest("POST", c.endpoint+path, bytes.NewReader(body))
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.endpoint+path, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,10 +98,10 @@ func (c *Client) Heartbeat(req wire.HeartbeatRequest) error {
|
||||
// bearer token for this worker. The current bearer is used for
|
||||
// authentication; the response carries the freshly issued token.
|
||||
//
|
||||
// Returns the new token string. The main app invalidates the old
|
||||
// token immediately.
|
||||
func (c *Client) RotateToken() (string, error) {
|
||||
resp, err := c.postJSON("/api/internal/workers/rotate-token", struct{}{})
|
||||
// Returns the new token string. The main app invalidates the old token
|
||||
// immediately.
|
||||
func (c *Client) RotateToken(ctx context.Context) (string, error) {
|
||||
resp, err := c.postJSONContext(ctx, "/api/internal/workers/rotate-token", struct{}{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -156,6 +163,12 @@ func (c *Client) ReportResults(req wire.ResultsRequest) error {
|
||||
|
||||
// WorkerSocket connects to the websocket task channel.
|
||||
func (c *Client) WorkerSocket() (*websocket.Conn, error) {
|
||||
return c.WorkerSocketContext(context.Background())
|
||||
}
|
||||
|
||||
// WorkerSocketContext connects to the websocket task channel, cancelling the
|
||||
// dial when the caller's context ends.
|
||||
func (c *Client) WorkerSocketContext(ctx context.Context) (*websocket.Conn, error) {
|
||||
endpoint := strings.TrimRight(c.endpoint, "/")
|
||||
wsURL := endpoint
|
||||
if !strings.HasSuffix(wsURL, "/worker") && !strings.HasSuffix(wsURL, "/api/worker") {
|
||||
@@ -175,7 +188,33 @@ func (c *Client) WorkerSocket() (*websocket.Conn, error) {
|
||||
q.Set("token", c.authToken)
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
conn, resp, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
dialer := *websocket.DefaultDialer
|
||||
var (
|
||||
connMu sync.Mutex
|
||||
dialConn net.Conn
|
||||
)
|
||||
stopClose := context.AfterFunc(ctx, func() {
|
||||
connMu.Lock()
|
||||
if dialConn != nil {
|
||||
_ = dialConn.Close()
|
||||
}
|
||||
connMu.Unlock()
|
||||
})
|
||||
defer stopClose()
|
||||
dialer.NetDialContext = func(dialCtx context.Context, network, address string) (net.Conn, error) {
|
||||
conn, err := (&net.Dialer{}).DialContext(dialCtx, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connMu.Lock()
|
||||
dialConn = conn
|
||||
if ctx.Err() != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
connMu.Unlock()
|
||||
return conn, nil
|
||||
}
|
||||
conn, resp, err := dialer.DialContext(ctx, u.String(), nil)
|
||||
if err != nil && resp != nil {
|
||||
defer resp.Body.Close() //nolint:errcheck
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
Ссылка в новой задаче
Block a user