mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(support): add client magicsock and agent prometheus metrics to support bundle (#12604)
* feat(codersdk): add ability to fetch prometheus metrics directly from agent * feat(support): add client magicsock and agent prometheus metrics to support bundle * refactor(support): simplify AgentInfo control flow Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
This commit is contained in:
co-authored by
Mathias Fredriksson
parent
4d9e6c0134
commit
b0c4e7504c
+117
-79
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -57,14 +58,16 @@ type Workspace struct {
|
||||
}
|
||||
|
||||
type Agent struct {
|
||||
Agent *codersdk.WorkspaceAgent `json:"agent"`
|
||||
ListeningPorts *codersdk.WorkspaceAgentListeningPortsResponse `json:"listening_ports"`
|
||||
Logs []byte `json:"logs"`
|
||||
MagicsockHTML []byte `json:"magicsock_html"`
|
||||
Manifest *agentsdk.Manifest `json:"manifest"`
|
||||
PeerDiagnostics *tailnet.PeerDiagnostics `json:"peer_diagnostics"`
|
||||
PingResult *ipnstate.PingResult `json:"ping_result"`
|
||||
StartupLogs []codersdk.WorkspaceAgentLog `json:"startup_logs"`
|
||||
Agent *codersdk.WorkspaceAgent `json:"agent"`
|
||||
ListeningPorts *codersdk.WorkspaceAgentListeningPortsResponse `json:"listening_ports"`
|
||||
Logs []byte `json:"logs"`
|
||||
ClientMagicsockHTML []byte `json:"client_magicsock_html"`
|
||||
AgentMagicsockHTML []byte `json:"agent_magicsock_html"`
|
||||
Manifest *agentsdk.Manifest `json:"manifest"`
|
||||
PeerDiagnostics *tailnet.PeerDiagnostics `json:"peer_diagnostics"`
|
||||
PingResult *ipnstate.PingResult `json:"ping_result"`
|
||||
Prometheus []byte `json:"prometheus"`
|
||||
StartupLogs []codersdk.WorkspaceAgentLog `json:"startup_logs"`
|
||||
}
|
||||
|
||||
// Deps is a set of dependencies for discovering information
|
||||
@@ -313,77 +316,10 @@ func AgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, ag
|
||||
return nil
|
||||
})
|
||||
|
||||
conn, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
|
||||
Logger: log.Named("dial-agent"),
|
||||
BlockEndpoints: false,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(ctx, "dial agent", slog.Error(err))
|
||||
} else {
|
||||
defer func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Error(ctx, "failed to close agent connection", slog.Error(err))
|
||||
}
|
||||
<-conn.Closed()
|
||||
}()
|
||||
if !conn.AwaitReachable(ctx) {
|
||||
log.Error(ctx, "timed out waiting for agent")
|
||||
} else {
|
||||
eg.Go(func() error {
|
||||
_, _, pingRes, err := conn.Ping(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("ping agent: %w", err)
|
||||
}
|
||||
a.PingResult = pingRes
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
pds := conn.GetPeerDiagnostics()
|
||||
a.PeerDiagnostics = &pds
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
msBytes, err := conn.DebugMagicsock(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get agent magicsock page: %w", err)
|
||||
}
|
||||
a.MagicsockHTML = msBytes
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
manifestRes, err := conn.DebugManifest(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch manifest: %w", err)
|
||||
}
|
||||
if err := json.NewDecoder(bytes.NewReader(manifestRes)).Decode(&a.Manifest); err != nil {
|
||||
return xerrors.Errorf("decode agent manifest: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
logBytes, err := conn.DebugLogs(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch coder agent logs: %w", err)
|
||||
}
|
||||
a.Logs = logBytes
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
lps, err := conn.ListeningPorts(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get listening ports: %w", err)
|
||||
}
|
||||
a.ListeningPorts = &lps
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
// to simplify control flow, fetching information directly from
|
||||
// the agent is handled in a separate function
|
||||
closer := connectedAgentInfo(ctx, client, log, agentID, &eg, &a)
|
||||
defer closer()
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
log.Error(ctx, "fetch agent information", slog.Error(err))
|
||||
@@ -392,6 +328,108 @@ func AgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, ag
|
||||
return a
|
||||
}
|
||||
|
||||
func connectedAgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, agentID uuid.UUID, eg *errgroup.Group, a *Agent) (closer func()) {
|
||||
conn, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
|
||||
Logger: log.Named("dial-agent"),
|
||||
BlockEndpoints: false,
|
||||
})
|
||||
|
||||
closer = func() {}
|
||||
|
||||
if err != nil {
|
||||
log.Error(ctx, "dial agent", slog.Error(err))
|
||||
return closer
|
||||
}
|
||||
|
||||
if !conn.AwaitReachable(ctx) {
|
||||
log.Error(ctx, "timed out waiting for agent")
|
||||
return closer
|
||||
}
|
||||
|
||||
closer = func() {
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Error(ctx, "failed to close agent connection", slog.Error(err))
|
||||
}
|
||||
<-conn.Closed()
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost/", nil)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create request: %w", err)
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
conn.MagicsockServeHTTPDebug(rr, req)
|
||||
a.ClientMagicsockHTML = rr.Body.Bytes()
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
promRes, err := conn.PrometheusMetrics(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch agent prometheus metrics: %w", err)
|
||||
}
|
||||
a.Prometheus = promRes
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
_, _, pingRes, err := conn.Ping(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("ping agent: %w", err)
|
||||
}
|
||||
a.PingResult = pingRes
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
pds := conn.GetPeerDiagnostics()
|
||||
a.PeerDiagnostics = &pds
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
msBytes, err := conn.DebugMagicsock(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get agent magicsock page: %w", err)
|
||||
}
|
||||
a.AgentMagicsockHTML = msBytes
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
manifestRes, err := conn.DebugManifest(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch manifest: %w", err)
|
||||
}
|
||||
if err := json.NewDecoder(bytes.NewReader(manifestRes)).Decode(&a.Manifest); err != nil {
|
||||
return xerrors.Errorf("decode agent manifest: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
logBytes, err := conn.DebugLogs(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch coder agent logs: %w", err)
|
||||
}
|
||||
a.Logs = logBytes
|
||||
return nil
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
lps, err := conn.ListeningPorts(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get listening ports: %w", err)
|
||||
}
|
||||
a.ListeningPorts = &lps
|
||||
return nil
|
||||
})
|
||||
|
||||
return closer
|
||||
}
|
||||
|
||||
// Run generates a support bundle with the given dependencies.
|
||||
func Run(ctx context.Context, d *Deps) (*Bundle, error) {
|
||||
var b Bundle
|
||||
|
||||
@@ -75,9 +75,11 @@ func TestRun(t *testing.T) {
|
||||
assertNotNilNotEmpty(t, bun.Agent.Agent, "agent should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.ListeningPorts, "agent listening ports should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.Logs, "agent logs should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.MagicsockHTML, "agent magicsock should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.AgentMagicsockHTML, "agent magicsock should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.ClientMagicsockHTML, "client magicsock should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.PeerDiagnostics, "agent peer diagnostics should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.PingResult, "agent ping result should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.Prometheus, "agent prometheus metrics should be present")
|
||||
assertNotNilNotEmpty(t, bun.Agent.StartupLogs, "agent startup logs should be present")
|
||||
assertNotNilNotEmpty(t, bun.Logs, "bundle logs should be present")
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user