diff --git a/agent/agent.go b/agent/agent.go index 6f601cbcd7..0cb2aa2aca 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -38,8 +38,6 @@ import ( "tailscale.com/util/clientmetric" "cdr.dev/slog" - "github.com/coder/retry" - "github.com/coder/coder/v2/agent/agentproc" "github.com/coder/coder/v2/agent/agentscripts" "github.com/coder/coder/v2/agent/agentssh" @@ -50,8 +48,10 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/tailnet" tailnetproto "github.com/coder/coder/v2/tailnet/proto" + "github.com/coder/retry" ) const ( @@ -1107,7 +1107,7 @@ func (a *agent) wireguardAddresses(agentID uuid.UUID) []netip.Prefix { netip.PrefixFrom(tailnet.IPFromUUID(agentID), 128), // We also listen on the legacy codersdk.WorkspaceAgentIP. This // allows for a transition away from wsconncache. - netip.PrefixFrom(codersdk.WorkspaceAgentIP, 128), + netip.PrefixFrom(workspacesdk.AgentIP, 128), } } @@ -1147,7 +1147,7 @@ func (a *agent) createTailnet(ctx context.Context, agentID uuid.UUID, derpMap *t } }() - sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.WorkspaceAgentSSHPort)) + sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentSSHPort)) if err != nil { return nil, xerrors.Errorf("listen on the ssh port: %w", err) } @@ -1162,7 +1162,7 @@ func (a *agent) createTailnet(ctx context.Context, agentID uuid.UUID, derpMap *t return nil, err } - reconnectingPTYListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.WorkspaceAgentReconnectingPTYPort)) + reconnectingPTYListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentReconnectingPTYPort)) if err != nil { return nil, xerrors.Errorf("listen for reconnecting pty: %w", err) } @@ -1211,7 +1211,7 @@ func (a *agent) createTailnet(ctx context.Context, agentID uuid.UUID, derpMap *t if err != nil { return } - var msg codersdk.WorkspaceAgentReconnectingPTYInit + var msg workspacesdk.AgentReconnectingPTYInit err = json.Unmarshal(data, &msg) if err != nil { logger.Warn(ctx, "failed to unmarshal init", slog.F("raw", data)) @@ -1225,7 +1225,7 @@ func (a *agent) createTailnet(ctx context.Context, agentID uuid.UUID, derpMap *t return nil, err } - speedtestListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.WorkspaceAgentSpeedtestPort)) + speedtestListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentSpeedtestPort)) if err != nil { return nil, xerrors.Errorf("listen for speedtest: %w", err) } @@ -1273,7 +1273,7 @@ func (a *agent) createTailnet(ctx context.Context, agentID uuid.UUID, derpMap *t return nil, err } - apiListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.WorkspaceAgentHTTPAPIServerPort)) + apiListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentHTTPAPIServerPort)) if err != nil { return nil, xerrors.Errorf("api listener: %w", err) } @@ -1386,7 +1386,7 @@ func (a *agent) runDERPMapSubscriber(ctx context.Context, conn drpc.Conn, networ } } -func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, msg codersdk.WorkspaceAgentReconnectingPTYInit, conn net.Conn) (retErr error) { +func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, msg workspacesdk.AgentReconnectingPTYInit, conn net.Conn) (retErr error) { defer conn.Close() a.metrics.connectionsTotal.Add(1) diff --git a/agent/agent_test.go b/agent/agent_test.go index 07258fee5e..2813d45125 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -46,7 +46,6 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" "cdr.dev/slog/sloggers/slogtest" - "github.com/coder/coder/v2/agent" "github.com/coder/coder/v2/agent/agentproc" "github.com/coder/coder/v2/agent/agentproc/agentproctest" @@ -55,6 +54,7 @@ import ( "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/tailnet" @@ -113,7 +113,7 @@ func TestAgent_Stats_ReconnectingPTY(t *testing.T) { require.NoError(t, err) defer ptyConn.Close() - data, err := json.Marshal(codersdk.ReconnectingPTYRequest{ + data, err := json.Marshal(workspacesdk.ReconnectingPTYRequest{ Data: "echo test\r\n", }) require.NoError(t, err) @@ -1606,7 +1606,7 @@ func TestAgent_ReconnectingPTY(t *testing.T) { require.NoError(t, tr1.ReadUntil(ctx, matchPrompt), "find prompt") require.NoError(t, tr2.ReadUntil(ctx, matchPrompt), "find prompt") - data, err := json.Marshal(codersdk.ReconnectingPTYRequest{ + data, err := json.Marshal(workspacesdk.ReconnectingPTYRequest{ Data: "echo test\r", }) require.NoError(t, err) @@ -1634,7 +1634,7 @@ func TestAgent_ReconnectingPTY(t *testing.T) { require.NoError(t, tr3.ReadUntil(ctx, matchEchoOutput), "find echo output") // Exit should cause the connection to close. - data, err = json.Marshal(codersdk.ReconnectingPTYRequest{ + data, err = json.Marshal(workspacesdk.ReconnectingPTYRequest{ Data: "exit\r", }) require.NoError(t, err) @@ -1783,7 +1783,7 @@ func TestAgent_UpdatedDERP(t *testing.T) { }) // Setup a client connection. - newClientConn := func(derpMap *tailcfg.DERPMap, name string) *codersdk.WorkspaceAgentConn { + newClientConn := func(derpMap *tailcfg.DERPMap, name string) *workspacesdk.AgentConn { conn, err := tailnet.NewConn(&tailnet.Options{ Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)}, DERPMap: derpMap, @@ -1812,9 +1812,9 @@ func TestAgent_UpdatedDERP(t *testing.T) { // Force DERP. conn.SetBlockEndpoints(true) - sdkConn := codersdk.NewWorkspaceAgentConn(conn, codersdk.WorkspaceAgentConnOptions{ + sdkConn := workspacesdk.NewAgentConn(conn, workspacesdk.AgentConnOptions{ AgentID: agentID, - CloseFunc: func() error { return codersdk.ErrSkipClose }, + CloseFunc: func() error { return workspacesdk.ErrSkipClose }, }) t.Cleanup(func() { t.Logf("closing sdkConn %s", name) @@ -2223,7 +2223,7 @@ func setupSSHSession( } func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Duration, opts ...func(*agenttest.Client, *agent.Options)) ( - *codersdk.WorkspaceAgentConn, + *workspacesdk.AgentConn, *agenttest.Client, <-chan *proto.Stats, afero.Fs, @@ -2296,7 +2296,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Durati t.Logf("error closing in-mem coordination: %s", err.Error()) } }) - agentConn := codersdk.NewWorkspaceAgentConn(conn, codersdk.WorkspaceAgentConnOptions{ + agentConn := workspacesdk.NewAgentConn(conn, workspacesdk.AgentConnOptions{ AgentID: metadata.AgentID, }) t.Cleanup(func() { diff --git a/agent/ports_supported.go b/agent/ports_supported.go index c6d7b34062..efa554de98 100644 --- a/agent/ports_supported.go +++ b/agent/ports_supported.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" ) func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) { @@ -32,7 +33,7 @@ func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentL seen := make(map[uint16]struct{}, len(tabs)) ports := []codersdk.WorkspaceAgentListeningPort{} for _, tab := range tabs { - if tab.LocalAddr == nil || tab.LocalAddr.Port < codersdk.WorkspaceAgentMinimumListeningPort { + if tab.LocalAddr == nil || tab.LocalAddr.Port < workspacesdk.AgentMinimumListeningPort { continue } diff --git a/agent/reconnectingpty/reconnectingpty.go b/agent/reconnectingpty/reconnectingpty.go index 280cf62aaa..fffe199f59 100644 --- a/agent/reconnectingpty/reconnectingpty.go +++ b/agent/reconnectingpty/reconnectingpty.go @@ -14,8 +14,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" - - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/pty" ) @@ -197,7 +196,7 @@ func (s *ptyState) waitForStateOrContext(ctx context.Context, state State) (Stat func readConnLoop(ctx context.Context, conn net.Conn, ptty pty.PTYCmd, metrics *prometheus.CounterVec, logger slog.Logger) { decoder := json.NewDecoder(conn) for { - var req codersdk.ReconnectingPTYRequest + var req workspacesdk.ReconnectingPTYRequest err := decoder.Decode(&req) if xerrors.Is(err, io.EOF) { return diff --git a/cli/agent_test.go b/cli/agent_test.go index fd71b962b9..9571bf03e1 100644 --- a/cli/agent_test.go +++ b/cli/agent_test.go @@ -19,6 +19,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/testutil" ) @@ -91,7 +92,8 @@ func TestWorkspaceAgent(t *testing.T) { if assert.NotEmpty(t, workspace.LatestBuild.Resources) && assert.NotEmpty(t, resources[0].Agents) { assert.NotEmpty(t, resources[0].Agents[0].Version) } - dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil) + dialer, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer dialer.Close() require.True(t, dialer.AwaitReachable(ctx)) @@ -130,7 +132,8 @@ func TestWorkspaceAgent(t *testing.T) { if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) { assert.NotEmpty(t, resources[0].Agents[0].Version) } - dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil) + dialer, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer dialer.Close() require.True(t, dialer.AwaitReachable(ctx)) @@ -173,7 +176,7 @@ func TestWorkspaceAgent(t *testing.T) { if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) { assert.NotEmpty(t, resources[0].Agents[0].Version) } - dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil) + dialer, err := workspacesdk.New(client).DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer dialer.Close() require.True(t, dialer.AwaitReachable(ctx)) diff --git a/cli/configssh_test.go b/cli/configssh_test.go index ee66e350c1..f1be8abe8b 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -25,6 +25,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbfake" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/pty/ptytest" "github.com/coder/coder/v2/testutil" @@ -83,7 +84,8 @@ func TestConfigSSH(t *testing.T) { }).WithAgent().Do() _ = agenttest.New(t, client.URL, r.AgentToken) resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID) - agentConn, err := client.DialWorkspaceAgent(context.Background(), resources[0].Agents[0].ID, nil) + agentConn, err := workspacesdk.New(client). + DialAgent(context.Background(), resources[0].Agents[0].ID, nil) require.NoError(t, err) defer agentConn.Close() diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index e92c3b5eee..3f5468635d 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -31,6 +31,7 @@ import ( "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/scaletest/agentconn" "github.com/coder/coder/v2/scaletest/createworkspaces" @@ -667,7 +668,7 @@ func (r *RootCmd) scaletestCreateWorkspaces() *serpent.Command { if runCommand != "" { config.ReconnectingPTY = &reconnectingpty.Config{ // AgentID is set by the test automatically. - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ ID: uuid.Nil, Height: 24, Width: 80, diff --git a/cli/netcheck.go b/cli/netcheck.go index 9035870e3f..fb4042b600 100644 --- a/cli/netcheck.go +++ b/cli/netcheck.go @@ -10,6 +10,7 @@ import ( "github.com/coder/coder/v2/coderd/healthcheck/derphealth" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/serpent" ) @@ -26,7 +27,7 @@ func (r *RootCmd) netcheck() *serpent.Command { ctx, cancel := context.WithTimeout(inv.Context(), 30*time.Second) defer cancel() - connInfo, err := client.WorkspaceAgentConnectionInfoGeneric(ctx) + connInfo, err := workspacesdk.New(client).AgentConnectionInfoGeneric(ctx) if err != nil { return err } diff --git a/cli/netcheck_test.go b/cli/netcheck_test.go index a2004a2224..45166861db 100644 --- a/cli/netcheck_test.go +++ b/cli/netcheck_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/cli/clitest" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" "github.com/coder/coder/v2/pty/ptytest" ) @@ -27,7 +27,7 @@ func TestNetcheck(t *testing.T) { b := out.Bytes() t.Log(string(b)) - var report codersdk.DERPHealthReport + var report healthsdk.DERPHealthReport require.NoError(t, json.Unmarshal(b, &report)) assert.True(t, report.Healthy) diff --git a/cli/ping.go b/cli/ping.go index 34f06d166b..17920c013e 100644 --- a/cli/ping.go +++ b/cli/ping.go @@ -14,6 +14,7 @@ import ( "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/serpent" ) @@ -55,10 +56,11 @@ func (r *RootCmd) ping() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger, - BlockEndpoints: r.disableDirect, - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ + Logger: logger, + BlockEndpoints: r.disableDirect, + }) if err != nil { return err } diff --git a/cli/portforward.go b/cli/portforward.go index ebe925a6a3..95ca8731f9 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -20,6 +20,7 @@ import ( "github.com/coder/coder/v2/agent/agentssh" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/serpent" ) @@ -102,10 +103,11 @@ func (r *RootCmd) portForward() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger, - BlockEndpoints: r.disableDirect, - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ + Logger: logger, + BlockEndpoints: r.disableDirect, + }) if err != nil { return err } @@ -193,7 +195,7 @@ func (r *RootCmd) portForward() *serpent.Command { func listenAndPortForward( ctx context.Context, inv *serpent.Invocation, - conn *codersdk.WorkspaceAgentConn, + conn *workspacesdk.AgentConn, wg *sync.WaitGroup, spec portForwardSpec, logger slog.Logger, diff --git a/cli/speedtest.go b/cli/speedtest.go index 20697295fe..dff9dabdf1 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -15,6 +15,7 @@ import ( "cdr.dev/slog/sloggers/sloghuman" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/serpent" ) @@ -59,9 +60,10 @@ func (r *RootCmd) speedtest() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger, - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ + Logger: logger, + }) if err != nil { return err } diff --git a/cli/ssh.go b/cli/ssh.go index 4e0f1799ee..fc788417e5 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -36,6 +36,7 @@ import ( "github.com/coder/coder/v2/coderd/autobuild/notify" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/cryptorand" ) @@ -222,10 +223,11 @@ func (r *RootCmd) ssh() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger, - BlockEndpoints: r.disableDirect, - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ + Logger: logger, + BlockEndpoints: r.disableDirect, + }) if err != nil { return xerrors.Errorf("dial agent: %w", err) } diff --git a/cli/support_test.go b/cli/support_test.go index 0cb0ba5bb5..7f2fce53e4 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -25,6 +25,8 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/healthsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/testutil" @@ -159,7 +161,7 @@ func assertBundleContents(t *testing.T, path string, badValues ...string) { decodeJSONFromZip(t, f, &v) require.NotEmpty(t, f, v, "experiments should not be empty") case "deployment/health.json": - var v codersdk.HealthcheckReport + var v healthsdk.HealthcheckReport decodeJSONFromZip(t, f, &v) require.NotEmpty(t, v, "health report should not be empty") case "network/coordinator_debug.html": @@ -169,7 +171,7 @@ func assertBundleContents(t *testing.T, path string, badValues ...string) { bs := readBytesFromZip(t, f) require.NotEmpty(t, bs, "tailnet debug should not be empty") case "network/netcheck.json": - var v codersdk.WorkspaceAgentConnectionInfo + var v workspacesdk.AgentConnectionInfo decodeJSONFromZip(t, f, &v) require.NotEmpty(t, v, "connection info should not be empty") case "workspace/workspace.json": diff --git a/cli/vscodessh.go b/cli/vscodessh.go index eec8ba775b..65f2bc7bd1 100644 --- a/cli/vscodessh.go +++ b/cli/vscodessh.go @@ -23,6 +23,7 @@ import ( "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/cli/cliutil" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/serpent" ) @@ -164,10 +165,11 @@ func (r *RootCmd) vscodeSSH() *serpent.Command { if r.disableDirect { logger.Info(ctx, "direct connections disabled") } - agentConn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger, - BlockEndpoints: r.disableDirect, - }) + agentConn, err := workspacesdk.New(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ + Logger: logger, + BlockEndpoints: r.disableDirect, + }) if err != nil { return xerrors.Errorf("dial workspace agent: %w", err) } @@ -280,7 +282,7 @@ type sshNetworkStats struct { DownloadBytesSec int64 `json:"download_bytes_sec"` } -func collectNetworkStats(ctx context.Context, agentConn *codersdk.WorkspaceAgentConn, start, end time.Time, counts map[netlogtype.Connection]netlogtype.Counts) (*sshNetworkStats, error) { +func collectNetworkStats(ctx context.Context, agentConn *workspacesdk.AgentConn, start, end time.Time, counts map[netlogtype.Connection]netlogtype.Counts) (*sshNetworkStats, error) { latency, p2p, pingResult, err := agentConn.Ping(ctx) if err != nil { return nil, err diff --git a/coderd/activitybump_test.go b/coderd/activitybump_test.go index 2f54a598e6..20c17b8d27 100644 --- a/coderd/activitybump_test.go +++ b/coderd/activitybump_test.go @@ -16,6 +16,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/testutil" ) @@ -165,9 +166,10 @@ func TestWorkspaceActivityBump(t *testing.T) { client, workspace, assertBumped := setupActivityTest(t) resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: slogtest.Make(t, nil), - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ + Logger: slogtest.Make(t, nil), + }) require.NoError(t, err) defer conn.Close() @@ -202,9 +204,10 @@ func TestWorkspaceActivityBump(t *testing.T) { // Bump by dialing the workspace and sending traffic. resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: slogtest.Make(t, nil), - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ + Logger: slogtest.Make(t, nil), + }) require.NoError(t, err) defer conn.Close() diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index c6c8be1d58..a082996d32 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -485,7 +485,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.HealthcheckReport" + "$ref": "#/definitions/healthsdk.HealthcheckReport" } } } @@ -510,7 +510,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.HealthSettings" + "$ref": "#/definitions/healthsdk.HealthSettings" } } } @@ -539,7 +539,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/codersdk.UpdateHealthSettings" + "$ref": "#/definitions/healthsdk.UpdateHealthSettings" } } ], @@ -547,7 +547,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.UpdateHealthSettings" + "$ref": "#/definitions/healthsdk.UpdateHealthSettings" } } } @@ -5479,7 +5479,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.AgentConnectionInfo" } } }, @@ -6088,7 +6088,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.AgentConnectionInfo" } } } @@ -8228,51 +8228,6 @@ const docTemplate = `{ "APIKeyScopeApplicationConnect" ] }, - "codersdk.AccessURLReport": { - "type": "object", - "properties": { - "access_url": { - "type": "string" - }, - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", - "type": "boolean" - }, - "healthz_response": { - "type": "string" - }, - "reachable": { - "type": "boolean" - }, - "severity": { - "enum": [ - "ok", - "warning", - "error" - ], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "status_code": { - "type": "integer" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.AddLicenseRequest": { "type": "object", "required": [ @@ -9180,126 +9135,6 @@ const docTemplate = `{ } } }, - "codersdk.DERPHealthReport": { - "type": "object", - "properties": { - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", - "type": "boolean" - }, - "netcheck": { - "$ref": "#/definitions/netcheck.Report" - }, - "netcheck_err": { - "type": "string" - }, - "netcheck_logs": { - "type": "array", - "items": { - "type": "string" - } - }, - "regions": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/codersdk.DERPRegionReport" - } - }, - "severity": { - "enum": [ - "ok", - "warning", - "error" - ], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, - "codersdk.DERPNodeReport": { - "type": "object", - "properties": { - "can_exchange_messages": { - "type": "boolean" - }, - "client_errs": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "client_logs": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", - "type": "boolean" - }, - "node": { - "$ref": "#/definitions/tailcfg.DERPNode" - }, - "node_info": { - "$ref": "#/definitions/derp.ServerInfoMessage" - }, - "round_trip_ping": { - "type": "string" - }, - "round_trip_ping_ms": { - "type": "integer" - }, - "severity": { - "enum": [ - "ok", - "warning", - "error" - ], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "stun": { - "$ref": "#/definitions/codersdk.STUNReport" - }, - "uses_websocket": { - "type": "boolean" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.DERPRegion": { "type": "object", "properties": { @@ -9311,45 +9146,6 @@ const docTemplate = `{ } } }, - "codersdk.DERPRegionReport": { - "type": "object", - "properties": { - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", - "type": "boolean" - }, - "node_reports": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.DERPNodeReport" - } - }, - "region": { - "$ref": "#/definitions/tailcfg.DERPRegion" - }, - "severity": { - "enum": [ - "ok", - "warning", - "error" - ], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.DERPServerConfig": { "type": "object", "properties": { @@ -9390,51 +9186,6 @@ const docTemplate = `{ } } }, - "codersdk.DatabaseReport": { - "type": "object", - "properties": { - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", - "type": "boolean" - }, - "latency": { - "type": "string" - }, - "latency_ms": { - "type": "integer" - }, - "reachable": { - "type": "boolean" - }, - "severity": { - "enum": [ - "ok", - "warning", - "error" - ], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "threshold_ms": { - "type": "integer" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.DeleteWorkspaceAgentPortShareRequest": { "type": "object", "properties": { @@ -10057,36 +9808,6 @@ const docTemplate = `{ "GroupSourceOIDC" ] }, - "codersdk.HealthSection": { - "type": "string", - "enum": [ - "DERP", - "AccessURL", - "Websocket", - "Database", - "WorkspaceProxy", - "ProvisionerDaemons" - ], - "x-enum-varnames": [ - "HealthSectionDERP", - "HealthSectionAccessURL", - "HealthSectionWebsocket", - "HealthSectionDatabase", - "HealthSectionWorkspaceProxy", - "HealthSectionProvisionerDaemons" - ] - }, - "codersdk.HealthSettings": { - "type": "object", - "properties": { - "dismissed_healthchecks": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.HealthSection" - } - } - } - }, "codersdk.Healthcheck": { "type": "object", "properties": { @@ -10115,62 +9836,6 @@ const docTemplate = `{ } } }, - "codersdk.HealthcheckReport": { - "type": "object", - "properties": { - "access_url": { - "$ref": "#/definitions/codersdk.AccessURLReport" - }, - "coder_version": { - "description": "The Coder version of the server that the report was generated on.", - "type": "string" - }, - "database": { - "$ref": "#/definitions/codersdk.DatabaseReport" - }, - "derp": { - "$ref": "#/definitions/codersdk.DERPHealthReport" - }, - "failing_sections": { - "description": "FailingSections is a list of sections that have failed their healthcheck.", - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.HealthSection" - } - }, - "healthy": { - "description": "Healthy is true if the report returns no errors.\nDeprecated: use ` + "`" + `Severity` + "`" + ` instead", - "type": "boolean" - }, - "provisioner_daemons": { - "$ref": "#/definitions/codersdk.ProvisionerDaemonsReport" - }, - "severity": { - "description": "Severity indicates the status of Coder health.", - "enum": [ - "ok", - "warning", - "error" - ], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "time": { - "description": "Time is the time the report was generated at.", - "type": "string", - "format": "date-time" - }, - "websocket": { - "$ref": "#/definitions/codersdk.WebsocketReport" - }, - "workspace_proxy": { - "$ref": "#/definitions/codersdk.WorkspaceProxyReport" - } - } - }, "codersdk.InsightsReportInterval": { "type": "string", "enum": [ @@ -10863,46 +10528,6 @@ const docTemplate = `{ } } }, - "codersdk.ProvisionerDaemonsReport": { - "type": "object", - "properties": { - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.ProvisionerDaemonsReportItem" - } - }, - "severity": { - "$ref": "#/definitions/health.Severity" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, - "codersdk.ProvisionerDaemonsReportItem": { - "type": "object", - "properties": { - "provisioner_daemon": { - "$ref": "#/definitions/codersdk.ProvisionerDaemon" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.ProvisionerJob": { "type": "object", "properties": { @@ -11426,20 +11051,6 @@ const docTemplate = `{ } } }, - "codersdk.STUNReport": { - "type": "object", - "properties": { - "canSTUN": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "error": { - "type": "string" - } - } - }, "codersdk.ServiceBannerConfig": { "type": "object", "properties": { @@ -12292,17 +11903,6 @@ const docTemplate = `{ } } }, - "codersdk.UpdateHealthSettings": { - "type": "object", - "properties": { - "dismissed_healthchecks": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.HealthSection" - } - } - } - }, "codersdk.UpdateRoles": { "type": "object", "properties": { @@ -12773,45 +12373,6 @@ const docTemplate = `{ } } }, - "codersdk.WebsocketReport": { - "type": "object", - "properties": { - "body": { - "type": "string" - }, - "code": { - "type": "integer" - }, - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", - "type": "boolean" - }, - "severity": { - "enum": [ - "ok", - "warning", - "error" - ], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "warnings": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "codersdk.Workspace": { "type": "object", "properties": { @@ -13062,20 +12623,6 @@ const docTemplate = `{ } } }, - "codersdk.WorkspaceAgentConnectionInfo": { - "type": "object", - "properties": { - "derp_force_websockets": { - "type": "boolean" - }, - "derp_map": { - "$ref": "#/definitions/tailcfg.DERPMap" - }, - "disable_direct_connections": { - "type": "boolean" - } - } - }, "codersdk.WorkspaceAgentHealth": { "type": "object", "properties": { @@ -13669,32 +13216,6 @@ const docTemplate = `{ } } }, - "codersdk.WorkspaceProxyReport": { - "type": "object", - "properties": { - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "type": "boolean" - }, - "severity": { - "$ref": "#/definitions/health.Severity" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - }, - "workspace_proxies": { - "$ref": "#/definitions/codersdk.RegionsResponse-codersdk_WorkspaceProxy" - } - } - }, "codersdk.WorkspaceProxyStatus": { "type": "object", "properties": { @@ -13949,6 +13470,471 @@ const docTemplate = `{ "SeverityError" ] }, + "healthsdk.AccessURLReport": { + "type": "object", + "properties": { + "access_url": { + "type": "string" + }, + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "type": "boolean" + }, + "healthz_response": { + "type": "string" + }, + "reachable": { + "type": "boolean" + }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "status_code": { + "type": "integer" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DERPHealthReport": { + "type": "object", + "properties": { + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "type": "boolean" + }, + "netcheck": { + "$ref": "#/definitions/netcheck.Report" + }, + "netcheck_err": { + "type": "string" + }, + "netcheck_logs": { + "type": "array", + "items": { + "type": "string" + } + }, + "regions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/healthsdk.DERPRegionReport" + } + }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DERPNodeReport": { + "type": "object", + "properties": { + "can_exchange_messages": { + "type": "boolean" + }, + "client_errs": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "client_logs": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "type": "boolean" + }, + "node": { + "$ref": "#/definitions/tailcfg.DERPNode" + }, + "node_info": { + "$ref": "#/definitions/derp.ServerInfoMessage" + }, + "round_trip_ping": { + "type": "string" + }, + "round_trip_ping_ms": { + "type": "integer" + }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "stun": { + "$ref": "#/definitions/healthsdk.STUNReport" + }, + "uses_websocket": { + "type": "boolean" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DERPRegionReport": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "type": "boolean" + }, + "node_reports": { + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.DERPNodeReport" + } + }, + "region": { + "$ref": "#/definitions/tailcfg.DERPRegion" + }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DatabaseReport": { + "type": "object", + "properties": { + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "type": "boolean" + }, + "latency": { + "type": "string" + }, + "latency_ms": { + "type": "integer" + }, + "reachable": { + "type": "boolean" + }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "threshold_ms": { + "type": "integer" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.HealthSection": { + "type": "string", + "enum": [ + "DERP", + "AccessURL", + "Websocket", + "Database", + "WorkspaceProxy", + "ProvisionerDaemons" + ], + "x-enum-varnames": [ + "HealthSectionDERP", + "HealthSectionAccessURL", + "HealthSectionWebsocket", + "HealthSectionDatabase", + "HealthSectionWorkspaceProxy", + "HealthSectionProvisionerDaemons" + ] + }, + "healthsdk.HealthSettings": { + "type": "object", + "properties": { + "dismissed_healthchecks": { + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.HealthSection" + } + } + } + }, + "healthsdk.HealthcheckReport": { + "type": "object", + "properties": { + "access_url": { + "$ref": "#/definitions/healthsdk.AccessURLReport" + }, + "coder_version": { + "description": "The Coder version of the server that the report was generated on.", + "type": "string" + }, + "database": { + "$ref": "#/definitions/healthsdk.DatabaseReport" + }, + "derp": { + "$ref": "#/definitions/healthsdk.DERPHealthReport" + }, + "failing_sections": { + "description": "FailingSections is a list of sections that have failed their healthcheck.", + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.HealthSection" + } + }, + "healthy": { + "description": "Healthy is true if the report returns no errors.\nDeprecated: use ` + "`" + `Severity` + "`" + ` instead", + "type": "boolean" + }, + "provisioner_daemons": { + "$ref": "#/definitions/healthsdk.ProvisionerDaemonsReport" + }, + "severity": { + "description": "Severity indicates the status of Coder health.", + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "time": { + "description": "Time is the time the report was generated at.", + "type": "string", + "format": "date-time" + }, + "websocket": { + "$ref": "#/definitions/healthsdk.WebsocketReport" + }, + "workspace_proxy": { + "$ref": "#/definitions/healthsdk.WorkspaceProxyReport" + } + } + }, + "healthsdk.ProvisionerDaemonsReport": { + "type": "object", + "properties": { + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.ProvisionerDaemonsReportItem" + } + }, + "severity": { + "$ref": "#/definitions/health.Severity" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.ProvisionerDaemonsReportItem": { + "type": "object", + "properties": { + "provisioner_daemon": { + "$ref": "#/definitions/codersdk.ProvisionerDaemon" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.STUNReport": { + "type": "object", + "properties": { + "canSTUN": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "error": { + "type": "string" + } + } + }, + "healthsdk.UpdateHealthSettings": { + "type": "object", + "properties": { + "dismissed_healthchecks": { + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.HealthSection" + } + } + } + }, + "healthsdk.WebsocketReport": { + "type": "object", + "properties": { + "body": { + "type": "string" + }, + "code": { + "type": "integer" + }, + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", + "type": "boolean" + }, + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "healthsdk.WorkspaceProxyReport": { + "type": "object", + "properties": { + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "type": "boolean" + }, + "severity": { + "$ref": "#/definitions/health.Severity" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + }, + "workspace_proxies": { + "$ref": "#/definitions/codersdk.RegionsResponse-codersdk_WorkspaceProxy" + } + } + }, "key.NodePublic": { "type": "object" }, @@ -14490,6 +14476,20 @@ const docTemplate = `{ } } }, + "workspacesdk.AgentConnectionInfo": { + "type": "object", + "properties": { + "derp_force_websockets": { + "type": "boolean" + }, + "derp_map": { + "$ref": "#/definitions/tailcfg.DERPMap" + }, + "disable_direct_connections": { + "type": "boolean" + } + } + }, "wsproxysdk.DeregisterWorkspaceProxyRequest": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 605d9f8c76..a559938463 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -413,7 +413,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.HealthcheckReport" + "$ref": "#/definitions/healthsdk.HealthcheckReport" } } } @@ -434,7 +434,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.HealthSettings" + "$ref": "#/definitions/healthsdk.HealthSettings" } } } @@ -457,7 +457,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/codersdk.UpdateHealthSettings" + "$ref": "#/definitions/healthsdk.UpdateHealthSettings" } } ], @@ -465,7 +465,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.UpdateHealthSettings" + "$ref": "#/definitions/healthsdk.UpdateHealthSettings" } } } @@ -4830,7 +4830,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.AgentConnectionInfo" } } }, @@ -5363,7 +5363,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.AgentConnectionInfo" } } } @@ -7303,47 +7303,6 @@ "enum": ["all", "application_connect"], "x-enum-varnames": ["APIKeyScopeAll", "APIKeyScopeApplicationConnect"] }, - "codersdk.AccessURLReport": { - "type": "object", - "properties": { - "access_url": { - "type": "string" - }, - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", - "type": "boolean" - }, - "healthz_response": { - "type": "string" - }, - "reachable": { - "type": "boolean" - }, - "severity": { - "enum": ["ok", "warning", "error"], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "status_code": { - "type": "integer" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.AddLicenseRequest": { "type": "object", "required": ["license"], @@ -8181,118 +8140,6 @@ } } }, - "codersdk.DERPHealthReport": { - "type": "object", - "properties": { - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", - "type": "boolean" - }, - "netcheck": { - "$ref": "#/definitions/netcheck.Report" - }, - "netcheck_err": { - "type": "string" - }, - "netcheck_logs": { - "type": "array", - "items": { - "type": "string" - } - }, - "regions": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/codersdk.DERPRegionReport" - } - }, - "severity": { - "enum": ["ok", "warning", "error"], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, - "codersdk.DERPNodeReport": { - "type": "object", - "properties": { - "can_exchange_messages": { - "type": "boolean" - }, - "client_errs": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "client_logs": { - "type": "array", - "items": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", - "type": "boolean" - }, - "node": { - "$ref": "#/definitions/tailcfg.DERPNode" - }, - "node_info": { - "$ref": "#/definitions/derp.ServerInfoMessage" - }, - "round_trip_ping": { - "type": "string" - }, - "round_trip_ping_ms": { - "type": "integer" - }, - "severity": { - "enum": ["ok", "warning", "error"], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "stun": { - "$ref": "#/definitions/codersdk.STUNReport" - }, - "uses_websocket": { - "type": "boolean" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.DERPRegion": { "type": "object", "properties": { @@ -8304,41 +8151,6 @@ } } }, - "codersdk.DERPRegionReport": { - "type": "object", - "properties": { - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", - "type": "boolean" - }, - "node_reports": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.DERPNodeReport" - } - }, - "region": { - "$ref": "#/definitions/tailcfg.DERPRegion" - }, - "severity": { - "enum": ["ok", "warning", "error"], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.DERPServerConfig": { "type": "object", "properties": { @@ -8379,47 +8191,6 @@ } } }, - "codersdk.DatabaseReport": { - "type": "object", - "properties": { - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", - "type": "boolean" - }, - "latency": { - "type": "string" - }, - "latency_ms": { - "type": "integer" - }, - "reachable": { - "type": "boolean" - }, - "severity": { - "enum": ["ok", "warning", "error"], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "threshold_ms": { - "type": "integer" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.DeleteWorkspaceAgentPortShareRequest": { "type": "object", "properties": { @@ -9028,36 +8799,6 @@ "enum": ["user", "oidc"], "x-enum-varnames": ["GroupSourceUser", "GroupSourceOIDC"] }, - "codersdk.HealthSection": { - "type": "string", - "enum": [ - "DERP", - "AccessURL", - "Websocket", - "Database", - "WorkspaceProxy", - "ProvisionerDaemons" - ], - "x-enum-varnames": [ - "HealthSectionDERP", - "HealthSectionAccessURL", - "HealthSectionWebsocket", - "HealthSectionDatabase", - "HealthSectionWorkspaceProxy", - "HealthSectionProvisionerDaemons" - ] - }, - "codersdk.HealthSettings": { - "type": "object", - "properties": { - "dismissed_healthchecks": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.HealthSection" - } - } - } - }, "codersdk.Healthcheck": { "type": "object", "properties": { @@ -9086,58 +8827,6 @@ } } }, - "codersdk.HealthcheckReport": { - "type": "object", - "properties": { - "access_url": { - "$ref": "#/definitions/codersdk.AccessURLReport" - }, - "coder_version": { - "description": "The Coder version of the server that the report was generated on.", - "type": "string" - }, - "database": { - "$ref": "#/definitions/codersdk.DatabaseReport" - }, - "derp": { - "$ref": "#/definitions/codersdk.DERPHealthReport" - }, - "failing_sections": { - "description": "FailingSections is a list of sections that have failed their healthcheck.", - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.HealthSection" - } - }, - "healthy": { - "description": "Healthy is true if the report returns no errors.\nDeprecated: use `Severity` instead", - "type": "boolean" - }, - "provisioner_daemons": { - "$ref": "#/definitions/codersdk.ProvisionerDaemonsReport" - }, - "severity": { - "description": "Severity indicates the status of Coder health.", - "enum": ["ok", "warning", "error"], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "time": { - "description": "Time is the time the report was generated at.", - "type": "string", - "format": "date-time" - }, - "websocket": { - "$ref": "#/definitions/codersdk.WebsocketReport" - }, - "workspace_proxy": { - "$ref": "#/definitions/codersdk.WorkspaceProxyReport" - } - } - }, "codersdk.InsightsReportInterval": { "type": "string", "enum": ["day", "week"], @@ -9775,46 +9464,6 @@ } } }, - "codersdk.ProvisionerDaemonsReport": { - "type": "object", - "properties": { - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.ProvisionerDaemonsReportItem" - } - }, - "severity": { - "$ref": "#/definitions/health.Severity" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, - "codersdk.ProvisionerDaemonsReportItem": { - "type": "object", - "properties": { - "provisioner_daemon": { - "$ref": "#/definitions/codersdk.ProvisionerDaemon" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - } - } - }, "codersdk.ProvisionerJob": { "type": "object", "properties": { @@ -10304,20 +9953,6 @@ } } }, - "codersdk.STUNReport": { - "type": "object", - "properties": { - "canSTUN": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "error": { - "type": "string" - } - } - }, "codersdk.ServiceBannerConfig": { "type": "object", "properties": { @@ -11127,17 +10762,6 @@ } } }, - "codersdk.UpdateHealthSettings": { - "type": "object", - "properties": { - "dismissed_healthchecks": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.HealthSection" - } - } - } - }, "codersdk.UpdateRoles": { "type": "object", "properties": { @@ -11575,41 +11199,6 @@ } } }, - "codersdk.WebsocketReport": { - "type": "object", - "properties": { - "body": { - "type": "string" - }, - "code": { - "type": "integer" - }, - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", - "type": "boolean" - }, - "severity": { - "enum": ["ok", "warning", "error"], - "allOf": [ - { - "$ref": "#/definitions/health.Severity" - } - ] - }, - "warnings": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, "codersdk.Workspace": { "type": "object", "properties": { @@ -11857,20 +11446,6 @@ } } }, - "codersdk.WorkspaceAgentConnectionInfo": { - "type": "object", - "properties": { - "derp_force_websockets": { - "type": "boolean" - }, - "derp_map": { - "$ref": "#/definitions/tailcfg.DERPMap" - }, - "disable_direct_connections": { - "type": "boolean" - } - } - }, "codersdk.WorkspaceAgentHealth": { "type": "object", "properties": { @@ -12421,32 +11996,6 @@ } } }, - "codersdk.WorkspaceProxyReport": { - "type": "object", - "properties": { - "dismissed": { - "type": "boolean" - }, - "error": { - "type": "string" - }, - "healthy": { - "type": "boolean" - }, - "severity": { - "$ref": "#/definitions/health.Severity" - }, - "warnings": { - "type": "array", - "items": { - "$ref": "#/definitions/health.Message" - } - }, - "workspace_proxies": { - "$ref": "#/definitions/codersdk.RegionsResponse-codersdk_WorkspaceProxy" - } - } - }, "codersdk.WorkspaceProxyStatus": { "type": "object", "properties": { @@ -12685,6 +12234,443 @@ "enum": ["ok", "warning", "error"], "x-enum-varnames": ["SeverityOK", "SeverityWarning", "SeverityError"] }, + "healthsdk.AccessURLReport": { + "type": "object", + "properties": { + "access_url": { + "type": "string" + }, + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", + "type": "boolean" + }, + "healthz_response": { + "type": "string" + }, + "reachable": { + "type": "boolean" + }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "status_code": { + "type": "integer" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DERPHealthReport": { + "type": "object", + "properties": { + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", + "type": "boolean" + }, + "netcheck": { + "$ref": "#/definitions/netcheck.Report" + }, + "netcheck_err": { + "type": "string" + }, + "netcheck_logs": { + "type": "array", + "items": { + "type": "string" + } + }, + "regions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/healthsdk.DERPRegionReport" + } + }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DERPNodeReport": { + "type": "object", + "properties": { + "can_exchange_messages": { + "type": "boolean" + }, + "client_errs": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "client_logs": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", + "type": "boolean" + }, + "node": { + "$ref": "#/definitions/tailcfg.DERPNode" + }, + "node_info": { + "$ref": "#/definitions/derp.ServerInfoMessage" + }, + "round_trip_ping": { + "type": "string" + }, + "round_trip_ping_ms": { + "type": "integer" + }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "stun": { + "$ref": "#/definitions/healthsdk.STUNReport" + }, + "uses_websocket": { + "type": "boolean" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DERPRegionReport": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", + "type": "boolean" + }, + "node_reports": { + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.DERPNodeReport" + } + }, + "region": { + "$ref": "#/definitions/tailcfg.DERPRegion" + }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DatabaseReport": { + "type": "object", + "properties": { + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", + "type": "boolean" + }, + "latency": { + "type": "string" + }, + "latency_ms": { + "type": "integer" + }, + "reachable": { + "type": "boolean" + }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "threshold_ms": { + "type": "integer" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.HealthSection": { + "type": "string", + "enum": [ + "DERP", + "AccessURL", + "Websocket", + "Database", + "WorkspaceProxy", + "ProvisionerDaemons" + ], + "x-enum-varnames": [ + "HealthSectionDERP", + "HealthSectionAccessURL", + "HealthSectionWebsocket", + "HealthSectionDatabase", + "HealthSectionWorkspaceProxy", + "HealthSectionProvisionerDaemons" + ] + }, + "healthsdk.HealthSettings": { + "type": "object", + "properties": { + "dismissed_healthchecks": { + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.HealthSection" + } + } + } + }, + "healthsdk.HealthcheckReport": { + "type": "object", + "properties": { + "access_url": { + "$ref": "#/definitions/healthsdk.AccessURLReport" + }, + "coder_version": { + "description": "The Coder version of the server that the report was generated on.", + "type": "string" + }, + "database": { + "$ref": "#/definitions/healthsdk.DatabaseReport" + }, + "derp": { + "$ref": "#/definitions/healthsdk.DERPHealthReport" + }, + "failing_sections": { + "description": "FailingSections is a list of sections that have failed their healthcheck.", + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.HealthSection" + } + }, + "healthy": { + "description": "Healthy is true if the report returns no errors.\nDeprecated: use `Severity` instead", + "type": "boolean" + }, + "provisioner_daemons": { + "$ref": "#/definitions/healthsdk.ProvisionerDaemonsReport" + }, + "severity": { + "description": "Severity indicates the status of Coder health.", + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "time": { + "description": "Time is the time the report was generated at.", + "type": "string", + "format": "date-time" + }, + "websocket": { + "$ref": "#/definitions/healthsdk.WebsocketReport" + }, + "workspace_proxy": { + "$ref": "#/definitions/healthsdk.WorkspaceProxyReport" + } + } + }, + "healthsdk.ProvisionerDaemonsReport": { + "type": "object", + "properties": { + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.ProvisionerDaemonsReportItem" + } + }, + "severity": { + "$ref": "#/definitions/health.Severity" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.ProvisionerDaemonsReportItem": { + "type": "object", + "properties": { + "provisioner_daemon": { + "$ref": "#/definitions/codersdk.ProvisionerDaemon" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.STUNReport": { + "type": "object", + "properties": { + "canSTUN": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "error": { + "type": "string" + } + } + }, + "healthsdk.UpdateHealthSettings": { + "type": "object", + "properties": { + "dismissed_healthchecks": { + "type": "array", + "items": { + "$ref": "#/definitions/healthsdk.HealthSection" + } + } + } + }, + "healthsdk.WebsocketReport": { + "type": "object", + "properties": { + "body": { + "type": "string" + }, + "code": { + "type": "integer" + }, + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", + "type": "boolean" + }, + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "healthsdk.WorkspaceProxyReport": { + "type": "object", + "properties": { + "dismissed": { + "type": "boolean" + }, + "error": { + "type": "string" + }, + "healthy": { + "type": "boolean" + }, + "severity": { + "$ref": "#/definitions/health.Severity" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + }, + "workspace_proxies": { + "$ref": "#/definitions/codersdk.RegionsResponse-codersdk_WorkspaceProxy" + } + } + }, "key.NodePublic": { "type": "object" }, @@ -13216,6 +13202,20 @@ } } }, + "workspacesdk.AgentConnectionInfo": { + "type": "object", + "properties": { + "derp_force_websockets": { + "type": "boolean" + }, + "derp_map": { + "$ref": "#/definitions/tailcfg.DERPMap" + }, + "disable_direct_connections": { + "type": "boolean" + } + } + }, "wsproxysdk.DeregisterWorkspaceProxyRequest": { "type": "object", "properties": { diff --git a/coderd/coderd.go b/coderd/coderd.go index 1de79b7e53..a367ab7cc5 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -37,7 +37,6 @@ import ( "tailscale.com/util/singleflight" "cdr.dev/slog" - agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/buildinfo" _ "github.com/coder/coder/v2/coderd/apidoc" // Used for swagger docs. @@ -70,6 +69,7 @@ import ( "github.com/coder/coder/v2/coderd/workspaceusage" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/drpc" + "github.com/coder/coder/v2/codersdk/healthsdk" "github.com/coder/coder/v2/provisionerd/proto" "github.com/coder/coder/v2/provisionersdk" "github.com/coder/coder/v2/site" @@ -153,7 +153,7 @@ type Options struct { // workspace applications. It consists of both a signing and encryption key. AppSecurityKey workspaceapps.SecurityKey - HealthcheckFunc func(ctx context.Context, apiKey string) *codersdk.HealthcheckReport + HealthcheckFunc func(ctx context.Context, apiKey string) *healthsdk.HealthcheckReport HealthcheckTimeout time.Duration HealthcheckRefresh time.Duration WorkspaceProxiesFetchUpdater *atomic.Pointer[healthcheck.WorkspaceProxiesFetchUpdater] @@ -423,7 +423,7 @@ func New(options *Options) *API { UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore, AccessControlStore: options.AccessControlStore, Experiments: experiments, - healthCheckGroup: &singleflight.Group[string, *codersdk.HealthcheckReport]{}, + healthCheckGroup: &singleflight.Group[string, *healthsdk.HealthcheckReport]{}, Acquirer: provisionerdserver.NewAcquirer( ctx, options.Logger.Named("acquirer"), @@ -462,7 +462,7 @@ func New(options *Options) *API { } if options.HealthcheckFunc == nil { - options.HealthcheckFunc = func(ctx context.Context, apiKey string) *codersdk.HealthcheckReport { + options.HealthcheckFunc = func(ctx context.Context, apiKey string) *healthsdk.HealthcheckReport { // NOTE: dismissed healthchecks are marked in formatHealthcheck. // Not here, as this result gets cached. return healthcheck.Run(ctx, &healthcheck.ReportOptions{ @@ -1266,8 +1266,8 @@ type API struct { // This is used to gate features that are not yet ready for production. Experiments codersdk.Experiments - healthCheckGroup *singleflight.Group[string, *codersdk.HealthcheckReport] - healthCheckCache atomic.Pointer[codersdk.HealthcheckReport] + healthCheckGroup *singleflight.Group[string, *healthsdk.HealthcheckReport] + healthCheckCache atomic.Pointer[healthsdk.HealthcheckReport] statsBatcher *batchstats.Batcher diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index a632b399e8..eb03e7ebcf 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -23,6 +23,7 @@ import ( "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbfake" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/agent/agenttest" @@ -189,9 +190,10 @@ func TestDERPForceWebSockets(t *testing.T) { t.Cleanup(func() { client.HTTPClient.CloseIdleConnections() }) + wsclient := workspacesdk.New(client) user := coderdtest.CreateFirstUser(t, client) - gen, err := client.WorkspaceAgentConnectionInfoGeneric(context.Background()) + gen, err := wsclient.AgentConnectionInfoGeneric(context.Background()) require.NoError(t, err) t.Log(spew.Sdump(gen)) @@ -213,8 +215,8 @@ func TestDERPForceWebSockets(t *testing.T) { defer cancel() resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, - &codersdk.DialWorkspaceAgentOptions{ + conn, err := wsclient.DialAgent(ctx, resources[0].Agents[0].ID, + &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug).Named("client"), }, ) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 60bec64787..b3288c84b8 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -75,6 +75,7 @@ import ( "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/codersdk/drpc" + "github.com/coder/coder/v2/codersdk/healthsdk" "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionerd" @@ -113,7 +114,7 @@ type Options struct { TemplateScheduleStore schedule.TemplateScheduleStore Coordinator tailnet.Coordinator - HealthcheckFunc func(ctx context.Context, apiKey string) *codersdk.HealthcheckReport + HealthcheckFunc func(ctx context.Context, apiKey string) *healthsdk.HealthcheckReport HealthcheckTimeout time.Duration HealthcheckRefresh time.Duration diff --git a/coderd/database/types.go b/coderd/database/types.go index 2beb74c868..b9f195e541 100644 --- a/coderd/database/types.go +++ b/coderd/database/types.go @@ -9,7 +9,7 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) // AuditOAuthConvertState is never stored in the database. It is stored in a cookie @@ -25,8 +25,8 @@ type AuditOAuthConvertState struct { } type HealthSettings struct { - ID uuid.UUID `db:"id" json:"id"` - DismissedHealthchecks []codersdk.HealthSection `db:"dismissed_healthchecks" json:"dismissed_healthchecks"` + ID uuid.UUID `db:"id" json:"id"` + DismissedHealthchecks []healthsdk.HealthSection `db:"dismissed_healthchecks" json:"dismissed_healthchecks"` } type Actions []rbac.Action diff --git a/coderd/debug.go b/coderd/debug.go index da40167c9c..d97edfe0b7 100644 --- a/coderd/debug.go +++ b/coderd/debug.go @@ -14,13 +14,13 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" - "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) // @Summary Debug Info Wireguard Coordinator @@ -50,7 +50,7 @@ func (api *API) debugTailnet(rw http.ResponseWriter, r *http.Request) { // @Security CoderSessionToken // @Produce json // @Tags Debug -// @Success 200 {object} codersdk.HealthcheckReport +// @Success 200 {object} healthsdk.HealthcheckReport // @Router /debug/health [get] // @Param force query boolean false "Force a healthcheck to run" func (api *API) debugDeploymentHealth(rw http.ResponseWriter, r *http.Request) { @@ -76,7 +76,7 @@ func (api *API) debugDeploymentHealth(rw http.ResponseWriter, r *http.Request) { } } - resChan := api.healthCheckGroup.DoChan("", func() (*codersdk.HealthcheckReport, error) { + resChan := api.healthCheckGroup.DoChan("", func() (*healthsdk.HealthcheckReport, error) { // Create a new context not tied to the request. ctx, cancel := context.WithTimeout(context.Background(), api.Options.HealthcheckTimeout) defer cancel() @@ -106,19 +106,19 @@ func (api *API) debugDeploymentHealth(rw http.ResponseWriter, r *http.Request) { } } -func formatHealthcheck(ctx context.Context, rw http.ResponseWriter, r *http.Request, hc codersdk.HealthcheckReport, dismissed ...codersdk.HealthSection) { +func formatHealthcheck(ctx context.Context, rw http.ResponseWriter, r *http.Request, hc healthsdk.HealthcheckReport, dismissed ...healthsdk.HealthSection) { // Mark any sections previously marked as dismissed. for _, d := range dismissed { switch d { - case codersdk.HealthSectionAccessURL: + case healthsdk.HealthSectionAccessURL: hc.AccessURL.Dismissed = true - case codersdk.HealthSectionDERP: + case healthsdk.HealthSectionDERP: hc.DERP.Dismissed = true - case codersdk.HealthSectionDatabase: + case healthsdk.HealthSectionDatabase: hc.Database.Dismissed = true - case codersdk.HealthSectionWebsocket: + case healthsdk.HealthSectionWebsocket: hc.Websocket.Dismissed = true - case codersdk.HealthSectionWorkspaceProxy: + case healthsdk.HealthSectionWorkspaceProxy: hc.WorkspaceProxy.Dismissed = true } } @@ -152,7 +152,7 @@ func formatHealthcheck(ctx context.Context, rw http.ResponseWriter, r *http.Requ // @Security CoderSessionToken // @Produce json // @Tags Debug -// @Success 200 {object} codersdk.HealthSettings +// @Success 200 {object} healthsdk.HealthSettings // @Router /debug/health/settings [get] func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request) { settingsJSON, err := api.Database.GetHealthSettings(r.Context()) @@ -164,7 +164,7 @@ func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request return } - var settings codersdk.HealthSettings + var settings healthsdk.HealthSettings err = json.Unmarshal([]byte(settingsJSON), &settings) if err != nil { httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{ @@ -175,7 +175,7 @@ func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request } if len(settings.DismissedHealthchecks) == 0 { - settings.DismissedHealthchecks = []codersdk.HealthSection{} + settings.DismissedHealthchecks = []healthsdk.HealthSection{} } httpapi.Write(r.Context(), rw, http.StatusOK, settings) @@ -187,8 +187,8 @@ func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request // @Accept json // @Produce json // @Tags Debug -// @Param request body codersdk.UpdateHealthSettings true "Update health settings" -// @Success 200 {object} codersdk.UpdateHealthSettings +// @Param request body healthsdk.UpdateHealthSettings true "Update health settings" +// @Success 200 {object} healthsdk.UpdateHealthSettings // @Router /debug/health/settings [put] func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -200,7 +200,7 @@ func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Requ return } - var settings codersdk.HealthSettings + var settings healthsdk.HealthSettings if !httpapi.Read(ctx, rw, r, &settings) { return } @@ -264,9 +264,9 @@ func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Requ httpapi.Write(r.Context(), rw, http.StatusOK, settings) } -func validateHealthSettings(settings codersdk.HealthSettings) error { +func validateHealthSettings(settings healthsdk.HealthSettings) error { for _, dismissed := range settings.DismissedHealthchecks { - ok := slices.Contains(codersdk.HealthSections, dismissed) + ok := slices.Contains(healthsdk.HealthSections, dismissed) if !ok { return xerrors.Errorf("unknown healthcheck section: %s", dismissed) } @@ -306,11 +306,11 @@ func _debugDERPTraffic(http.ResponseWriter, *http.Request) {} //nolint:unused // @x-apidocgen {"skip": true} func _debugExpVar(http.ResponseWriter, *http.Request) {} //nolint:unused -func loadDismissedHealthchecks(ctx context.Context, db database.Store, logger slog.Logger) []codersdk.HealthSection { - dismissedHealthchecks := []codersdk.HealthSection{} +func loadDismissedHealthchecks(ctx context.Context, db database.Store, logger slog.Logger) []healthsdk.HealthSection { + dismissedHealthchecks := []healthsdk.HealthSection{} settingsJSON, err := db.GetHealthSettings(ctx) if err == nil { - var settings codersdk.HealthSettings + var settings healthsdk.HealthSettings err = json.Unmarshal([]byte(settingsJSON), &settings) if len(settings.DismissedHealthchecks) > 0 { dismissedHealthchecks = settings.DismissedHealthchecks diff --git a/coderd/debug_test.go b/coderd/debug_test.go index b3ba003755..0d5dfd1885 100644 --- a/coderd/debug_test.go +++ b/coderd/debug_test.go @@ -15,7 +15,7 @@ import ( "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/v2/coderd/coderdtest" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" "github.com/coder/coder/v2/testutil" ) @@ -29,10 +29,10 @@ func TestDebugHealth(t *testing.T) { ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort) sessionToken string client = coderdtest.New(t, &coderdtest.Options{ - HealthcheckFunc: func(_ context.Context, apiKey string) *codersdk.HealthcheckReport { + HealthcheckFunc: func(_ context.Context, apiKey string) *healthsdk.HealthcheckReport { calls.Add(1) assert.Equal(t, sessionToken, apiKey) - return &codersdk.HealthcheckReport{ + return &healthsdk.HealthcheckReport{ Time: time.Now(), } }, @@ -62,10 +62,10 @@ func TestDebugHealth(t *testing.T) { ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort) sessionToken string client = coderdtest.New(t, &coderdtest.Options{ - HealthcheckFunc: func(_ context.Context, apiKey string) *codersdk.HealthcheckReport { + HealthcheckFunc: func(_ context.Context, apiKey string) *healthsdk.HealthcheckReport { calls.Add(1) assert.Equal(t, sessionToken, apiKey) - return &codersdk.HealthcheckReport{ + return &healthsdk.HealthcheckReport{ Time: time.Now(), } }, @@ -97,15 +97,15 @@ func TestDebugHealth(t *testing.T) { client = coderdtest.New(t, &coderdtest.Options{ Logger: &logger, HealthcheckTimeout: time.Microsecond, - HealthcheckFunc: func(context.Context, string) *codersdk.HealthcheckReport { + HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport { t := time.NewTimer(time.Second) defer t.Stop() select { case <-ctx.Done(): - return &codersdk.HealthcheckReport{} + return &healthsdk.HealthcheckReport{} case <-t.C: - return &codersdk.HealthcheckReport{} + return &healthsdk.HealthcheckReport{} } }, }) @@ -129,9 +129,9 @@ func TestDebugHealth(t *testing.T) { ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort) client = coderdtest.New(t, &coderdtest.Options{ HealthcheckRefresh: time.Microsecond, - HealthcheckFunc: func(context.Context, string) *codersdk.HealthcheckReport { + HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport { calls <- struct{}{} - return &codersdk.HealthcheckReport{} + return &healthsdk.HealthcheckReport{} }, }) _ = coderdtest.CreateFirstUser(t, client) @@ -174,9 +174,9 @@ func TestDebugHealth(t *testing.T) { client = coderdtest.New(t, &coderdtest.Options{ HealthcheckRefresh: time.Hour, HealthcheckTimeout: time.Hour, - HealthcheckFunc: func(context.Context, string) *codersdk.HealthcheckReport { + HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport { calls++ - return &codersdk.HealthcheckReport{ + return &healthsdk.HealthcheckReport{ Time: time.Now(), } }, @@ -208,12 +208,12 @@ func TestDebugHealth(t *testing.T) { ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort) sessionToken string client = coderdtest.New(t, &coderdtest.Options{ - HealthcheckFunc: func(_ context.Context, apiKey string) *codersdk.HealthcheckReport { + HealthcheckFunc: func(_ context.Context, apiKey string) *healthsdk.HealthcheckReport { assert.Equal(t, sessionToken, apiKey) - return &codersdk.HealthcheckReport{ + return &healthsdk.HealthcheckReport{ Time: time.Now(), Healthy: true, - DERP: codersdk.DERPHealthReport{Healthy: true}, + DERP: healthsdk.DERPHealthReport{Healthy: true}, } }, }) @@ -251,11 +251,11 @@ func TestHealthSettings(t *testing.T) { _ = coderdtest.CreateFirstUser(t, adminClient) // when - settings, err := adminClient.HealthSettings(ctx) + settings, err := healthsdk.New(adminClient).HealthSettings(ctx) require.NoError(t, err) // then - require.Equal(t, codersdk.HealthSettings{DismissedHealthchecks: []codersdk.HealthSection{}}, settings) + require.Equal(t, healthsdk.HealthSettings{DismissedHealthchecks: []healthsdk.HealthSection{}}, settings) }) t.Run("DismissSection", func(t *testing.T) { @@ -268,16 +268,16 @@ func TestHealthSettings(t *testing.T) { adminClient := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, adminClient) - expected := codersdk.HealthSettings{ - DismissedHealthchecks: []codersdk.HealthSection{codersdk.HealthSectionDERP, codersdk.HealthSectionWebsocket}, + expected := healthsdk.HealthSettings{ + DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, } // when: dismiss "derp" and "websocket" - err := adminClient.PutHealthSettings(ctx, expected) + err := healthsdk.New(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // then - settings, err := adminClient.HealthSettings(ctx) + settings, err := healthsdk.New(adminClient).HealthSettings(ctx) require.NoError(t, err) require.Equal(t, expected, settings) @@ -287,7 +287,7 @@ func TestHealthSettings(t *testing.T) { bs, err := io.ReadAll(res.Body) require.NoError(t, err) defer res.Body.Close() - var hc codersdk.HealthcheckReport + var hc healthsdk.HealthcheckReport require.NoError(t, json.Unmarshal(bs, &hc)) require.True(t, hc.DERP.Dismissed) require.True(t, hc.Websocket.Dismissed) @@ -303,23 +303,23 @@ func TestHealthSettings(t *testing.T) { adminClient := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, adminClient) - initial := codersdk.HealthSettings{ - DismissedHealthchecks: []codersdk.HealthSection{codersdk.HealthSectionDERP, codersdk.HealthSectionWebsocket}, + initial := healthsdk.HealthSettings{ + DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, } - err := adminClient.PutHealthSettings(ctx, initial) + err := healthsdk.New(adminClient).PutHealthSettings(ctx, initial) require.NoError(t, err) - expected := codersdk.HealthSettings{ - DismissedHealthchecks: []codersdk.HealthSection{codersdk.HealthSectionDERP}, + expected := healthsdk.HealthSettings{ + DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP}, } // when: undismiss "websocket" - err = adminClient.PutHealthSettings(ctx, expected) + err = healthsdk.New(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // then - settings, err := adminClient.HealthSettings(ctx) + settings, err := healthsdk.New(adminClient).HealthSettings(ctx) require.NoError(t, err) require.Equal(t, expected, settings) @@ -329,7 +329,7 @@ func TestHealthSettings(t *testing.T) { bs, err := io.ReadAll(res.Body) require.NoError(t, err) defer res.Body.Close() - var hc codersdk.HealthcheckReport + var hc healthsdk.HealthcheckReport require.NoError(t, json.Unmarshal(bs, &hc)) require.True(t, hc.DERP.Dismissed) require.False(t, hc.Websocket.Dismissed) @@ -345,15 +345,15 @@ func TestHealthSettings(t *testing.T) { adminClient := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, adminClient) - expected := codersdk.HealthSettings{ - DismissedHealthchecks: []codersdk.HealthSection{codersdk.HealthSectionDERP, codersdk.HealthSectionWebsocket}, + expected := healthsdk.HealthSettings{ + DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, } - err := adminClient.PutHealthSettings(ctx, expected) + err := healthsdk.New(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // when - err = adminClient.PutHealthSettings(ctx, expected) + err = healthsdk.New(adminClient).PutHealthSettings(ctx, expected) // then require.Error(t, err) diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index 53ca693a9b..2115285a30 100644 --- a/coderd/healthcheck/accessurl.go +++ b/coderd/healthcheck/accessurl.go @@ -8,10 +8,10 @@ import ( "time" "github.com/coder/coder/v2/coderd/healthcheck/health" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) -type AccessURLReport codersdk.AccessURLReport +type AccessURLReport healthsdk.AccessURLReport type AccessURLReportOptions struct { AccessURL *url.URL diff --git a/coderd/healthcheck/database.go b/coderd/healthcheck/database.go index 5d8455da87..275124c5b1 100644 --- a/coderd/healthcheck/database.go +++ b/coderd/healthcheck/database.go @@ -8,14 +8,14 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/healthcheck/health" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) const ( DatabaseDefaultThreshold = 15 * time.Millisecond ) -type DatabaseReport codersdk.DatabaseReport +type DatabaseReport healthsdk.DatabaseReport type DatabaseReportOptions struct { DB database.Store diff --git a/coderd/healthcheck/derphealth/derp.go b/coderd/healthcheck/derphealth/derp.go index d3f99fbed0..e72c527d70 100644 --- a/coderd/healthcheck/derphealth/derp.go +++ b/coderd/healthcheck/derphealth/derp.go @@ -25,7 +25,7 @@ import ( "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/coderd/util/slice" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) const ( @@ -40,15 +40,15 @@ type ReportOptions struct { DERPMap *tailcfg.DERPMap } -type Report codersdk.DERPHealthReport +type Report healthsdk.DERPHealthReport type RegionReport struct { - codersdk.DERPRegionReport + healthsdk.DERPRegionReport mu sync.Mutex } type NodeReport struct { - codersdk.DERPNodeReport + healthsdk.DERPNodeReport mu sync.Mutex clientCounter int } @@ -59,7 +59,7 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) { r.Warnings = []health.Message{} r.Dismissed = opts.Dismissed - r.Regions = map[int]*codersdk.DERPRegionReport{} + r.Regions = map[int]*healthsdk.DERPRegionReport{} wg := &sync.WaitGroup{} mu := sync.Mutex{} @@ -69,7 +69,7 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) { var ( region = region regionReport = RegionReport{ - DERPRegionReport: codersdk.DERPRegionReport{ + DERPRegionReport: healthsdk.DERPRegionReport{ Region: region, }, } @@ -121,7 +121,7 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) { func (r *RegionReport) Run(ctx context.Context) { r.Healthy = true r.Severity = health.SeverityOK - r.NodeReports = []*codersdk.DERPNodeReport{} + r.NodeReports = []*healthsdk.DERPNodeReport{} r.Warnings = []health.Message{} wg := &sync.WaitGroup{} @@ -132,7 +132,7 @@ func (r *RegionReport) Run(ctx context.Context) { var ( node = node nodeReport = NodeReport{ - DERPNodeReport: codersdk.DERPNodeReport{ + DERPNodeReport: healthsdk.DERPNodeReport{ Node: node, Healthy: true, }, @@ -499,8 +499,8 @@ func convertError(err error) *string { return nil } -func sortNodeReports(reports []*codersdk.DERPNodeReport) { - slices.SortFunc(reports, func(a, b *codersdk.DERPNodeReport) int { +func sortNodeReports(reports []*healthsdk.DERPNodeReport) { + slices.SortFunc(reports, func(a, b *healthsdk.DERPNodeReport) int { return slice.Ascending(a.Node.Name, b.Node.Name) }) } diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index 0b058dea35..c724347721 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -9,16 +9,16 @@ import ( "github.com/coder/coder/v2/coderd/healthcheck/derphealth" "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/coderd/util/ptr" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) type Checker interface { - DERP(ctx context.Context, opts *derphealth.ReportOptions) codersdk.DERPHealthReport - AccessURL(ctx context.Context, opts *AccessURLReportOptions) codersdk.AccessURLReport - Websocket(ctx context.Context, opts *WebsocketReportOptions) codersdk.WebsocketReport - Database(ctx context.Context, opts *DatabaseReportOptions) codersdk.DatabaseReport - WorkspaceProxy(ctx context.Context, opts *WorkspaceProxyReportOptions) codersdk.WorkspaceProxyReport - ProvisionerDaemons(ctx context.Context, opts *ProvisionerDaemonsReportDeps) codersdk.ProvisionerDaemonsReport + DERP(ctx context.Context, opts *derphealth.ReportOptions) healthsdk.DERPHealthReport + AccessURL(ctx context.Context, opts *AccessURLReportOptions) healthsdk.AccessURLReport + Websocket(ctx context.Context, opts *WebsocketReportOptions) healthsdk.WebsocketReport + Database(ctx context.Context, opts *DatabaseReportOptions) healthsdk.DatabaseReport + WorkspaceProxy(ctx context.Context, opts *WorkspaceProxyReportOptions) healthsdk.WorkspaceProxyReport + ProvisionerDaemons(ctx context.Context, opts *ProvisionerDaemonsReportDeps) healthsdk.ProvisionerDaemonsReport } type ReportOptions struct { @@ -34,46 +34,46 @@ type ReportOptions struct { type defaultChecker struct{} -func (defaultChecker) DERP(ctx context.Context, opts *derphealth.ReportOptions) codersdk.DERPHealthReport { +func (defaultChecker) DERP(ctx context.Context, opts *derphealth.ReportOptions) healthsdk.DERPHealthReport { var report derphealth.Report report.Run(ctx, opts) - return codersdk.DERPHealthReport(report) + return healthsdk.DERPHealthReport(report) } -func (defaultChecker) AccessURL(ctx context.Context, opts *AccessURLReportOptions) codersdk.AccessURLReport { +func (defaultChecker) AccessURL(ctx context.Context, opts *AccessURLReportOptions) healthsdk.AccessURLReport { var report AccessURLReport report.Run(ctx, opts) - return codersdk.AccessURLReport(report) + return healthsdk.AccessURLReport(report) } -func (defaultChecker) Websocket(ctx context.Context, opts *WebsocketReportOptions) codersdk.WebsocketReport { +func (defaultChecker) Websocket(ctx context.Context, opts *WebsocketReportOptions) healthsdk.WebsocketReport { var report WebsocketReport report.Run(ctx, opts) - return codersdk.WebsocketReport(report) + return healthsdk.WebsocketReport(report) } -func (defaultChecker) Database(ctx context.Context, opts *DatabaseReportOptions) codersdk.DatabaseReport { +func (defaultChecker) Database(ctx context.Context, opts *DatabaseReportOptions) healthsdk.DatabaseReport { var report DatabaseReport report.Run(ctx, opts) - return codersdk.DatabaseReport(report) + return healthsdk.DatabaseReport(report) } -func (defaultChecker) WorkspaceProxy(ctx context.Context, opts *WorkspaceProxyReportOptions) codersdk.WorkspaceProxyReport { +func (defaultChecker) WorkspaceProxy(ctx context.Context, opts *WorkspaceProxyReportOptions) healthsdk.WorkspaceProxyReport { var report WorkspaceProxyReport report.Run(ctx, opts) - return codersdk.WorkspaceProxyReport(report) + return healthsdk.WorkspaceProxyReport(report) } -func (defaultChecker) ProvisionerDaemons(ctx context.Context, opts *ProvisionerDaemonsReportDeps) codersdk.ProvisionerDaemonsReport { +func (defaultChecker) ProvisionerDaemons(ctx context.Context, opts *ProvisionerDaemonsReportDeps) healthsdk.ProvisionerDaemonsReport { var report ProvisionerDaemonsReport report.Run(ctx, opts) - return codersdk.ProvisionerDaemonsReport(report) + return healthsdk.ProvisionerDaemonsReport(report) } -func Run(ctx context.Context, opts *ReportOptions) *codersdk.HealthcheckReport { +func Run(ctx context.Context, opts *ReportOptions) *healthsdk.HealthcheckReport { var ( wg sync.WaitGroup - report codersdk.HealthcheckReport + report healthsdk.HealthcheckReport ) if opts.Checker == nil { @@ -156,24 +156,24 @@ func Run(ctx context.Context, opts *ReportOptions) *codersdk.HealthcheckReport { wg.Wait() report.Time = time.Now() - report.FailingSections = []codersdk.HealthSection{} + report.FailingSections = []healthsdk.HealthSection{} if report.DERP.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, codersdk.HealthSectionDERP) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionDERP) } if report.AccessURL.Severity.Value() > health.SeverityOK.Value() { - report.FailingSections = append(report.FailingSections, codersdk.HealthSectionAccessURL) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionAccessURL) } if report.Websocket.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, codersdk.HealthSectionWebsocket) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionWebsocket) } if report.Database.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, codersdk.HealthSectionDatabase) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionDatabase) } if report.WorkspaceProxy.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, codersdk.HealthSectionWorkspaceProxy) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionWorkspaceProxy) } if report.ProvisionerDaemons.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, codersdk.HealthSectionProvisionerDaemons) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionProvisionerDaemons) } report.Healthy = len(report.FailingSections) == 0 diff --git a/coderd/healthcheck/healthcheck_test.go b/coderd/healthcheck/healthcheck_test.go index 01cd08fb7a..bb5cd581db 100644 --- a/coderd/healthcheck/healthcheck_test.go +++ b/coderd/healthcheck/healthcheck_test.go @@ -9,39 +9,39 @@ import ( "github.com/coder/coder/v2/coderd/healthcheck" "github.com/coder/coder/v2/coderd/healthcheck/derphealth" "github.com/coder/coder/v2/coderd/healthcheck/health" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) type testChecker struct { - DERPReport codersdk.DERPHealthReport - AccessURLReport codersdk.AccessURLReport - WebsocketReport codersdk.WebsocketReport - DatabaseReport codersdk.DatabaseReport - WorkspaceProxyReport codersdk.WorkspaceProxyReport - ProvisionerDaemonsReport codersdk.ProvisionerDaemonsReport + DERPReport healthsdk.DERPHealthReport + AccessURLReport healthsdk.AccessURLReport + WebsocketReport healthsdk.WebsocketReport + DatabaseReport healthsdk.DatabaseReport + WorkspaceProxyReport healthsdk.WorkspaceProxyReport + ProvisionerDaemonsReport healthsdk.ProvisionerDaemonsReport } -func (c *testChecker) DERP(context.Context, *derphealth.ReportOptions) codersdk.DERPHealthReport { +func (c *testChecker) DERP(context.Context, *derphealth.ReportOptions) healthsdk.DERPHealthReport { return c.DERPReport } -func (c *testChecker) AccessURL(context.Context, *healthcheck.AccessURLReportOptions) codersdk.AccessURLReport { +func (c *testChecker) AccessURL(context.Context, *healthcheck.AccessURLReportOptions) healthsdk.AccessURLReport { return c.AccessURLReport } -func (c *testChecker) Websocket(context.Context, *healthcheck.WebsocketReportOptions) codersdk.WebsocketReport { +func (c *testChecker) Websocket(context.Context, *healthcheck.WebsocketReportOptions) healthsdk.WebsocketReport { return c.WebsocketReport } -func (c *testChecker) Database(context.Context, *healthcheck.DatabaseReportOptions) codersdk.DatabaseReport { +func (c *testChecker) Database(context.Context, *healthcheck.DatabaseReportOptions) healthsdk.DatabaseReport { return c.DatabaseReport } -func (c *testChecker) WorkspaceProxy(context.Context, *healthcheck.WorkspaceProxyReportOptions) codersdk.WorkspaceProxyReport { +func (c *testChecker) WorkspaceProxy(context.Context, *healthcheck.WorkspaceProxyReportOptions) healthsdk.WorkspaceProxyReport { return c.WorkspaceProxyReport } -func (c *testChecker) ProvisionerDaemons(context.Context, *healthcheck.ProvisionerDaemonsReportDeps) codersdk.ProvisionerDaemonsReport { +func (c *testChecker) ProvisionerDaemons(context.Context, *healthcheck.ProvisionerDaemonsReportDeps) healthsdk.ProvisionerDaemonsReport { return c.ProvisionerDaemonsReport } @@ -53,346 +53,346 @@ func TestHealthcheck(t *testing.T) { checker *testChecker healthy bool severity health.Severity - failingSections []codersdk.HealthSection + failingSections []healthsdk.HealthSection }{{ name: "OK", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Severity: health.SeverityOK, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Severity: health.SeverityOK, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityOK, }, }, healthy: true, severity: health.SeverityOK, - failingSections: []codersdk.HealthSection{}, + failingSections: []healthsdk.HealthSection{}, }, { name: "DERPFail", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: false, Severity: health.SeverityError, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Severity: health.SeverityOK, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityOK, }, }, healthy: false, severity: health.SeverityError, - failingSections: []codersdk.HealthSection{codersdk.HealthSectionDERP}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionDERP}, }, { name: "DERPWarning", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}}, Severity: health.SeverityWarning, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Severity: health.SeverityOK, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityOK, }, }, healthy: true, severity: health.SeverityWarning, - failingSections: []codersdk.HealthSection{}, + failingSections: []healthsdk.HealthSection{}, }, { name: "AccessURLFail", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Severity: health.SeverityOK, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: false, Severity: health.SeverityWarning, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Severity: health.SeverityOK, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityOK, }, }, healthy: false, severity: health.SeverityWarning, - failingSections: []codersdk.HealthSection{codersdk.HealthSectionAccessURL}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionAccessURL}, }, { name: "WebsocketFail", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Severity: health.SeverityOK, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: false, Severity: health.SeverityError, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Severity: health.SeverityOK, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityOK, }, }, healthy: false, severity: health.SeverityError, - failingSections: []codersdk.HealthSection{codersdk.HealthSectionWebsocket}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionWebsocket}, }, { name: "DatabaseFail", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Severity: health.SeverityOK, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: false, Severity: health.SeverityError, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Severity: health.SeverityOK, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityOK, }, }, healthy: false, severity: health.SeverityError, - failingSections: []codersdk.HealthSection{codersdk.HealthSectionDatabase}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionDatabase}, }, { name: "ProxyFail", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Severity: health.SeverityOK, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: false, Severity: health.SeverityError, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityOK, }, }, severity: health.SeverityError, healthy: false, - failingSections: []codersdk.HealthSection{codersdk.HealthSectionWorkspaceProxy}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionWorkspaceProxy}, }, { name: "ProxyWarn", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Severity: health.SeverityOK, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}}, Severity: health.SeverityWarning, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityOK, }, }, severity: health.SeverityWarning, healthy: true, - failingSections: []codersdk.HealthSection{}, + failingSections: []healthsdk.HealthSection{}, }, { name: "ProvisionerDaemonsFail", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Severity: health.SeverityOK, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Severity: health.SeverityOK, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityError, }, }, severity: health.SeverityError, healthy: false, - failingSections: []codersdk.HealthSection{codersdk.HealthSectionProvisionerDaemons}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionProvisionerDaemons}, }, { name: "ProvisionerDaemonsWarn", checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: true, Severity: health.SeverityOK, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: true, Severity: health.SeverityOK, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: true, Severity: health.SeverityOK, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: true, Severity: health.SeverityOK, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: true, Severity: health.SeverityOK, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityWarning, Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}}, }, }, severity: health.SeverityWarning, healthy: true, - failingSections: []codersdk.HealthSection{}, + failingSections: []healthsdk.HealthSection{}, }, { name: "AllFail", healthy: false, checker: &testChecker{ - DERPReport: codersdk.DERPHealthReport{ + DERPReport: healthsdk.DERPHealthReport{ Healthy: false, Severity: health.SeverityError, }, - AccessURLReport: codersdk.AccessURLReport{ + AccessURLReport: healthsdk.AccessURLReport{ Healthy: false, Severity: health.SeverityError, }, - WebsocketReport: codersdk.WebsocketReport{ + WebsocketReport: healthsdk.WebsocketReport{ Healthy: false, Severity: health.SeverityError, }, - DatabaseReport: codersdk.DatabaseReport{ + DatabaseReport: healthsdk.DatabaseReport{ Healthy: false, Severity: health.SeverityError, }, - WorkspaceProxyReport: codersdk.WorkspaceProxyReport{ + WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{ Healthy: false, Severity: health.SeverityError, }, - ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{ + ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{ Severity: health.SeverityError, }, }, severity: health.SeverityError, - failingSections: []codersdk.HealthSection{ - codersdk.HealthSectionDERP, - codersdk.HealthSectionAccessURL, - codersdk.HealthSectionWebsocket, - codersdk.HealthSectionDatabase, - codersdk.HealthSectionWorkspaceProxy, - codersdk.HealthSectionProvisionerDaemons, + failingSections: []healthsdk.HealthSection{ + healthsdk.HealthSectionDERP, + healthsdk.HealthSectionAccessURL, + healthsdk.HealthSectionWebsocket, + healthsdk.HealthSectionDatabase, + healthsdk.HealthSectionWorkspaceProxy, + healthsdk.HealthSectionProvisionerDaemons, }, }} { c := c diff --git a/coderd/healthcheck/provisioner.go b/coderd/healthcheck/provisioner.go index f30f067fb5..b15899dc09 100644 --- a/coderd/healthcheck/provisioner.go +++ b/coderd/healthcheck/provisioner.go @@ -16,11 +16,11 @@ import ( "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/coderd/provisionerdserver" "github.com/coder/coder/v2/coderd/util/ptr" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" "github.com/coder/coder/v2/provisionerd/proto" ) -type ProvisionerDaemonsReport codersdk.ProvisionerDaemonsReport +type ProvisionerDaemonsReport healthsdk.ProvisionerDaemonsReport type ProvisionerDaemonsReportDeps struct { // Required @@ -40,7 +40,7 @@ type ProvisionerDaemonsStore interface { } func (r *ProvisionerDaemonsReport) Run(ctx context.Context, opts *ProvisionerDaemonsReportDeps) { - r.Items = make([]codersdk.ProvisionerDaemonsReportItem, 0) + r.Items = make([]healthsdk.ProvisionerDaemonsReportItem, 0) r.Severity = health.SeverityOK r.Warnings = make([]health.Message, 0) r.Dismissed = opts.Dismissed @@ -95,7 +95,7 @@ func (r *ProvisionerDaemonsReport) Run(ctx context.Context, opts *ProvisionerDae continue } - it := codersdk.ProvisionerDaemonsReportItem{ + it := healthsdk.ProvisionerDaemonsReportItem{ ProvisionerDaemon: db2sdk.ProvisionerDaemon(daemon), Warnings: make([]health.Message, 0), } diff --git a/coderd/healthcheck/provisioner_test.go b/coderd/healthcheck/provisioner_test.go index 7942f5a7dd..37530f9f8c 100644 --- a/coderd/healthcheck/provisioner_test.go +++ b/coderd/healthcheck/provisioner_test.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" + gomock "go.uber.org/mock/gomock" "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbmock" @@ -15,9 +16,8 @@ import ( "github.com/coder/coder/v2/coderd/healthcheck" "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" "github.com/coder/coder/v2/provisionerd/proto" - - gomock "go.uber.org/mock/gomock" ) func TestProvisionerDaemonReport(t *testing.T) { @@ -34,21 +34,21 @@ func TestProvisionerDaemonReport(t *testing.T) { expectedSeverity health.Severity expectedWarningCode health.Code expectedError string - expectedItems []codersdk.ProvisionerDaemonsReportItem + expectedItems []healthsdk.ProvisionerDaemonsReportItem }{ { name: "current version empty", currentVersion: "", expectedSeverity: health.SeverityError, expectedError: "Developer error: CurrentVersion is empty", - expectedItems: []codersdk.ProvisionerDaemonsReportItem{}, + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{}, }, { name: "no daemons", currentVersion: "v1.2.3", currentAPIMajorVersion: proto.CurrentMajor, expectedSeverity: health.SeverityError, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{}, + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{}, expectedWarningCode: health.CodeProvisionerDaemonsNoProvisionerDaemons, }, { @@ -58,7 +58,7 @@ func TestProvisionerDaemonReport(t *testing.T) { provisionerDaemonsErr: assert.AnError, expectedSeverity: health.SeverityError, expectedError: assert.AnError.Error(), - expectedItems: []codersdk.ProvisionerDaemonsReportItem{}, + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{}, }, { name: "one daemon up to date", @@ -66,7 +66,7 @@ func TestProvisionerDaemonReport(t *testing.T) { currentAPIMajorVersion: proto.CurrentMajor, expectedSeverity: health.SeverityOK, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-ok", "v1.2.3", "1.0", now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{ + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{ { ProvisionerDaemon: codersdk.ProvisionerDaemon{ ID: uuid.Nil, @@ -89,7 +89,7 @@ func TestProvisionerDaemonReport(t *testing.T) { expectedSeverity: health.SeverityWarning, expectedWarningCode: health.CodeProvisionerDaemonVersionMismatch, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-old", "v1.1.2", "1.0", now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{ + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{ { ProvisionerDaemon: codersdk.ProvisionerDaemon{ ID: uuid.Nil, @@ -117,7 +117,7 @@ func TestProvisionerDaemonReport(t *testing.T) { expectedSeverity: health.SeverityError, expectedWarningCode: health.CodeUnknown, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-invalid-version", "invalid", "1.0", now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{ + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{ { ProvisionerDaemon: codersdk.ProvisionerDaemon{ ID: uuid.Nil, @@ -145,7 +145,7 @@ func TestProvisionerDaemonReport(t *testing.T) { expectedSeverity: health.SeverityError, expectedWarningCode: health.CodeUnknown, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-invalid-api", "v1.2.3", "invalid", now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{ + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{ { ProvisionerDaemon: codersdk.ProvisionerDaemon{ ID: uuid.Nil, @@ -173,7 +173,7 @@ func TestProvisionerDaemonReport(t *testing.T) { expectedSeverity: health.SeverityWarning, expectedWarningCode: health.CodeProvisionerDaemonAPIMajorVersionDeprecated, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-old-api", "v2.3.4", "1.0", now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{ + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{ { ProvisionerDaemon: codersdk.ProvisionerDaemon{ ID: uuid.Nil, @@ -201,7 +201,7 @@ func TestProvisionerDaemonReport(t *testing.T) { expectedSeverity: health.SeverityWarning, expectedWarningCode: health.CodeProvisionerDaemonVersionMismatch, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-ok", "v1.2.3", "1.0", now), fakeProvisionerDaemon(t, "pd-old", "v1.1.2", "1.0", now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{ + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{ { ProvisionerDaemon: codersdk.ProvisionerDaemon{ ID: uuid.Nil, @@ -242,7 +242,7 @@ func TestProvisionerDaemonReport(t *testing.T) { expectedSeverity: health.SeverityWarning, expectedWarningCode: health.CodeProvisionerDaemonVersionMismatch, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-ok", "v1.2.3", "1.0", now), fakeProvisionerDaemon(t, "pd-new", "v2.3.4", "1.0", now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{ + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{ { ProvisionerDaemon: codersdk.ProvisionerDaemon{ ID: uuid.Nil, @@ -282,7 +282,7 @@ func TestProvisionerDaemonReport(t *testing.T) { currentAPIMajorVersion: proto.CurrentMajor, expectedSeverity: health.SeverityOK, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemonStale(t, "pd-stale", "v1.2.3", "0.9", now.Add(-5*time.Minute), now), fakeProvisionerDaemon(t, "pd-ok", "v2.3.4", "1.0", now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{ + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{ { ProvisionerDaemon: codersdk.ProvisionerDaemon{ ID: uuid.Nil, @@ -305,7 +305,7 @@ func TestProvisionerDaemonReport(t *testing.T) { expectedSeverity: health.SeverityError, expectedWarningCode: health.CodeProvisionerDaemonsNoProvisionerDaemons, provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemonStale(t, "pd-ok", "v1.2.3", "0.9", now.Add(-5*time.Minute), now)}, - expectedItems: []codersdk.ProvisionerDaemonsReportItem{}, + expectedItems: []healthsdk.ProvisionerDaemonsReportItem{}, }, } { tt := tt diff --git a/coderd/healthcheck/websocket.go b/coderd/healthcheck/websocket.go index 582a5023ef..75ee3cdf05 100644 --- a/coderd/healthcheck/websocket.go +++ b/coderd/healthcheck/websocket.go @@ -13,10 +13,10 @@ import ( "nhooyr.io/websocket" "github.com/coder/coder/v2/coderd/healthcheck/health" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) -type WebsocketReport codersdk.WebsocketReport +type WebsocketReport healthsdk.WebsocketReport type WebsocketReportOptions struct { APIKey string diff --git a/coderd/healthcheck/workspaceproxy.go b/coderd/healthcheck/workspaceproxy.go index 5302eea76e..d9fdfd5295 100644 --- a/coderd/healthcheck/workspaceproxy.go +++ b/coderd/healthcheck/workspaceproxy.go @@ -9,9 +9,10 @@ import ( "github.com/coder/coder/v2/coderd/healthcheck/health" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/healthsdk" ) -type WorkspaceProxyReport codersdk.WorkspaceProxyReport +type WorkspaceProxyReport healthsdk.WorkspaceProxyReport type WorkspaceProxyReportOptions struct { WorkspaceProxiesFetchUpdater WorkspaceProxiesFetchUpdater diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 916d3e6870..c62ef41b70 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -32,6 +32,7 @@ import ( "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/testutil" @@ -86,9 +87,10 @@ func TestDeploymentInsights(t *testing.T) { require.NoError(t, err) assert.NotZero(t, res.Workspaces[0].LastUsedAt) - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: slogtest.Make(t, nil).Named("tailnet"), - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ + Logger: slogtest.Make(t, nil).Named("tailnet"), + }) require.NoError(t, err) defer func() { _ = conn.Close() @@ -174,9 +176,10 @@ func TestUserActivityInsights_SanityCheck(t *testing.T) { defer cancel() // Connect to the agent to generate usage/latency stats. - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger.Named("client"), - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ + Logger: logger.Named("client"), + }) require.NoError(t, err) defer conn.Close() @@ -271,9 +274,10 @@ func TestUserLatencyInsights(t *testing.T) { defer cancel() // Connect to the agent to generate usage/latency stats. - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger.Named("client"), - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ + Logger: logger.Named("client"), + }) require.NoError(t, err) defer conn.Close() diff --git a/coderd/tailnet.go b/coderd/tailnet.go index 4fbae175bf..f684b05cd2 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -23,7 +23,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/coderd/workspaceapps" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/site" "github.com/coder/coder/v2/tailnet" "github.com/coder/retry" @@ -427,9 +427,9 @@ func (s *ServerTailnet) acquireTicket(agentID uuid.UUID) (release func()) { } } -func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*codersdk.WorkspaceAgentConn, func(), error) { +func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*workspacesdk.AgentConn, func(), error) { var ( - conn *codersdk.WorkspaceAgentConn + conn *workspacesdk.AgentConn ret func() ) @@ -440,9 +440,9 @@ func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*code } ret = s.acquireTicket(agentID) - conn = codersdk.NewWorkspaceAgentConn(s.conn, codersdk.WorkspaceAgentConnOptions{ + conn = workspacesdk.NewAgentConn(s.conn, workspacesdk.AgentConnOptions{ AgentID: agentID, - CloseFunc: func() error { return codersdk.ErrSkipClose }, + CloseFunc: func() error { return workspacesdk.ErrSkipClose }, }) // Since we now have an open conn, be careful to close it if we error diff --git a/coderd/tailnet_test.go b/coderd/tailnet_test.go index 51fdae5ea2..b7b7ad1df9 100644 --- a/coderd/tailnet_test.go +++ b/coderd/tailnet_test.go @@ -26,8 +26,8 @@ import ( "github.com/coder/coder/v2/agent/agenttest" "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd" - "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/tailnet/tailnettest" "github.com/coder/coder/v2/testutil" @@ -80,7 +80,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { agents, serverTailnet := setupServerTailnetAgent(t, 1) a := agents[0] - u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", codersdk.WorkspaceAgentHTTPAPIServerPort)) + u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) rp := serverTailnet.ReverseProxy(u, u, a.id) @@ -111,7 +111,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { registry := prometheus.NewRegistry() require.NoError(t, registry.Register(serverTailnet)) - u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", codersdk.WorkspaceAgentHTTPAPIServerPort)) + u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) rp := serverTailnet.ReverseProxy(u, u, a.id) @@ -145,7 +145,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { agents, serverTailnet := setupServerTailnetAgent(t, 1) a := agents[0] - u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", codersdk.WorkspaceAgentHTTPAPIServerPort)) + u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) rp := serverTailnet.ReverseProxy(u, u, a.id) @@ -156,7 +156,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { // Ensure the reverse proxy director rewrites the url host to the agent's IP. rp.Director(req) assert.Equal(t, - fmt.Sprintf("[%s]:%d", tailnet.IPFromUUID(a.id).String(), codersdk.WorkspaceAgentHTTPAPIServerPort), + fmt.Sprintf("[%s]:%d", tailnet.IPFromUUID(a.id).String(), workspacesdk.AgentHTTPAPIServerPort), req.URL.Host, ) }) @@ -315,7 +315,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) { require.True(t, serverTailnet.Conn().GetBlockEndpoints(), "expected BlockEndpoints to be set") - u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", codersdk.WorkspaceAgentHTTPAPIServerPort)) + u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort)) require.NoError(t, err) rp := serverTailnet.ReverseProxy(u, u, a.id) diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 087044c293..f0d6bf1433 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -22,6 +22,7 @@ import ( "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/testutil" ) @@ -1235,9 +1236,10 @@ func TestTemplateMetrics(t *testing.T) { require.NoError(t, err) assert.Zero(t, res.Workspaces[0].LastUsedAt) - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: slogtest.Make(t, nil).Named("tailnet"), - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ + Logger: slogtest.Make(t, nil).Named("tailnet"), + }) require.NoError(t, err) defer func() { _ = conn.Close() diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 4d07735bd9..34170b3bf7 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -39,6 +39,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/tailnet/proto" ) @@ -803,13 +804,13 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req // common non-HTTP ports such as databases, FTP, SSH, etc. filteredPorts := make([]codersdk.WorkspaceAgentListeningPort, 0, len(portsResponse.Ports)) for _, port := range portsResponse.Ports { - if port.Port < codersdk.WorkspaceAgentMinimumListeningPort { + if port.Port < workspacesdk.AgentMinimumListeningPort { continue } if _, ok := appPorts[port.Port]; ok { continue } - if _, ok := codersdk.WorkspaceAgentIgnoredListeningPorts[port.Port]; ok { + if _, ok := workspacesdk.AgentIgnoredListeningPorts[port.Port]; ok { continue } filteredPorts = append(filteredPorts, port) @@ -825,12 +826,12 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req // @Produce json // @Tags Agents // @Param workspaceagent path string true "Workspace agent ID" format(uuid) -// @Success 200 {object} codersdk.WorkspaceAgentConnectionInfo +// @Success 200 {object} workspacesdk.AgentConnectionInfo // @Router /workspaceagents/{workspaceagent}/connection [get] func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() - httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{ + httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.AgentConnectionInfo{ DERPMap: api.DERPMap(), DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(), DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(), @@ -845,13 +846,13 @@ func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request // @Security CoderSessionToken // @Produce json // @Tags Agents -// @Success 200 {object} codersdk.WorkspaceAgentConnectionInfo +// @Success 200 {object} workspacesdk.AgentConnectionInfo // @Router /workspaceagents/connection [get] // @x-apidocgen {"skip": true} func (api *API) workspaceAgentConnectionGeneric(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() - httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{ + httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.AgentConnectionInfo{ DERPMap: api.DERPMap(), DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(), DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(), diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 68a1492e79..73f0c74cd7 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -37,6 +37,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/tailnet/tailnettest" @@ -337,7 +338,8 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil) + conn, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer func() { _ = conn.Close() @@ -448,13 +450,14 @@ func TestWorkspaceAgentTailnet(t *testing.T) { _ = agenttest.New(t, client.URL, r.AgentToken) resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID) - conn, err := func() (*codersdk.WorkspaceAgentConn, error) { + conn, err := func() (*workspacesdk.AgentConn, error) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() // Connection should remain open even if the dial context is canceled. - return client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), - }) + return workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ + Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), + }) }() require.NoError(t, err) defer conn.Close() @@ -547,7 +550,7 @@ func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) { require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusOK, res.StatusCode) - var connInfo codersdk.WorkspaceAgentConnectionInfo + var connInfo workspacesdk.AgentConnectionInfo err = json.NewDecoder(res.Body).Decode(&connInfo) require.NoError(t, err) require.True(t, connInfo.DisableDirectConnections) @@ -563,9 +566,10 @@ func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) { } } - conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{ - Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ + Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), + }) require.NoError(t, err) defer conn.Close() @@ -603,10 +607,10 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) { } willFilterPort := func(port int) bool { - if port < codersdk.WorkspaceAgentMinimumListeningPort || port > 65535 { + if port < workspacesdk.AgentMinimumListeningPort || port > 65535 { return true } - if _, ok := codersdk.WorkspaceAgentIgnoredListeningPorts[uint16(port)]; ok { + if _, ok := workspacesdk.AgentIgnoredListeningPorts[uint16(port)]; ok { return true } @@ -646,7 +650,7 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) { port uint16 ) require.Eventually(t, func() bool { - for ignoredPort := range codersdk.WorkspaceAgentIgnoredListeningPorts { + for ignoredPort := range workspacesdk.AgentIgnoredListeningPorts { if ignoredPort < 1024 || ignoredPort == 5432 { continue } @@ -1623,13 +1627,14 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) { agentID := resources[0].Agents[0].ID // Connect from a client. - conn1, err := func() (*codersdk.WorkspaceAgentConn, error) { + conn1, err := func() (*workspacesdk.AgentConn, error) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() // Connection should remain open even if the dial context is canceled. - return client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger.Named("client1"), - }) + return workspacesdk.New(client). + DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ + Logger: logger.Named("client1"), + }) }() require.NoError(t, err) defer conn1.Close() @@ -1672,9 +1677,10 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) { require.True(t, ok) // Connect from a second client. - conn2, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger.Named("client2"), - }) + conn2, err := workspacesdk.New(client). + DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ + Logger: logger.Named("client2"), + }) require.NoError(t, err) defer conn2.Close() ok = conn2.AwaitReachable(ctx) diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go index c532713894..dea232c867 100644 --- a/coderd/workspaceapps/apptest/apptest.go +++ b/coderd/workspaceapps/apptest/apptest.go @@ -30,6 +30,7 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/testutil" ) @@ -950,7 +951,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) { defer cancel() app := appDetails.Apps.Port - app.AppSlugOrPort = strconv.Itoa(codersdk.WorkspaceAgentMinimumListeningPort - 1) + app.AppSlugOrPort = strconv.Itoa(workspacesdk.AgentMinimumListeningPort - 1) resp, err := requestWithRetries(ctx, t, appDetails.AppClient(t), http.MethodGet, appDetails.SubdomainAppURL(app).String(), nil) require.NoError(t, err) defer resp.Body.Close() @@ -1695,7 +1696,7 @@ func (r *fakeStatsReporter) Report(_ context.Context, stats []workspaceapps.Stat } func testReconnectingPTY(ctx context.Context, t *testing.T, client *codersdk.Client, agentID uuid.UUID, signedToken string) { - opts := codersdk.WorkspaceAgentReconnectingPTYOpts{ + opts := workspacesdk.WorkspaceAgentReconnectingPTYOpts{ AgentID: agentID, Reconnect: uuid.New(), Width: 80, @@ -1720,7 +1721,7 @@ func testReconnectingPTY(ctx context.Context, t *testing.T, client *codersdk.Cli return strings.Contains(line, "exit") || strings.Contains(line, "logout") } - conn, err := client.WorkspaceAgentReconnectingPTY(ctx, opts) + conn, err := workspacesdk.New(client).AgentReconnectingPTY(ctx, opts) require.NoError(t, err) defer conn.Close() @@ -1729,7 +1730,7 @@ func testReconnectingPTY(ctx context.Context, t *testing.T, client *codersdk.Cli // will sometimes put the command output on the same line as the command and the test will flake require.NoError(t, tr.ReadUntil(ctx, matchPrompt), "find prompt") - data, err := json.Marshal(codersdk.ReconnectingPTYRequest{ + data, err := json.Marshal(workspacesdk.ReconnectingPTYRequest{ Data: "echo test\r", }) require.NoError(t, err) @@ -1740,7 +1741,7 @@ func testReconnectingPTY(ctx context.Context, t *testing.T, client *codersdk.Cli require.NoError(t, tr.ReadUntil(ctx, matchEchoOutput), "find echo output") // Exit should cause the connection to close. - data, err = json.Marshal(codersdk.ReconnectingPTYRequest{ + data, err = json.Marshal(workspacesdk.ReconnectingPTYRequest{ Data: "exit\r", }) require.NoError(t, err) diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go index 70d8a64efa..5b14cbf340 100644 --- a/coderd/workspaceapps/proxy.go +++ b/coderd/workspaceapps/proxy.go @@ -26,6 +26,7 @@ import ( "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/coderd/workspaceapps/appurl" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/site" ) @@ -68,7 +69,7 @@ type AgentProvider interface { ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID) *httputil.ReverseProxy // AgentConn returns a new connection to the specified agent. - AgentConn(ctx context.Context, agentID uuid.UUID) (_ *codersdk.WorkspaceAgentConn, release func(), _ error) + AgentConn(ctx context.Context, agentID uuid.UUID) (_ *workspacesdk.AgentConn, release func(), _ error) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) @@ -513,9 +514,11 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT return } - if portInt < codersdk.WorkspaceAgentMinimumListeningPort { + if portInt < workspacesdk.AgentMinimumListeningPort { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: fmt.Sprintf("Application port %d is not permitted. Coder reserves ports less than %d for internal use.", portInt, codersdk.WorkspaceAgentMinimumListeningPort), + Message: fmt.Sprintf("Application port %d is not permitted. Coder reserves ports less than %d for internal use.", + portInt, workspacesdk.AgentMinimumListeningPort, + ), }) return } diff --git a/codersdk/health.go b/codersdk/healthsdk/healthsdk.go similarity index 85% rename from codersdk/health.go rename to codersdk/healthsdk/healthsdk.go index faa252464e..26e72e4e5f 100644 --- a/codersdk/health.go +++ b/codersdk/healthsdk/healthsdk.go @@ -1,4 +1,4 @@ -package codersdk +package healthsdk import ( "context" @@ -6,15 +6,24 @@ import ( "net/http" "time" + "golang.org/x/xerrors" "tailscale.com/derp" "tailscale.com/net/netcheck" "tailscale.com/tailcfg" "github.com/coder/coder/v2/coderd/healthcheck/health" - - "golang.org/x/xerrors" + "github.com/coder/coder/v2/codersdk" ) +// @typescript-ignore HealthClient +type HealthClient struct { + client *codersdk.Client +} + +func New(c *codersdk.Client) *HealthClient { + return &HealthClient{client: c} +} + type HealthSection string // If you add another const below, make sure to add it to HealthSections! @@ -44,34 +53,34 @@ type UpdateHealthSettings struct { DismissedHealthchecks []HealthSection `json:"dismissed_healthchecks"` } -func (c *Client) DebugHealth(ctx context.Context) (HealthcheckReport, error) { - res, err := c.Request(ctx, http.MethodGet, "/api/v2/debug/health", nil) +func (c *HealthClient) DebugHealth(ctx context.Context) (HealthcheckReport, error) { + res, err := c.client.Request(ctx, http.MethodGet, "/api/v2/debug/health", nil) if err != nil { return HealthcheckReport{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return HealthcheckReport{}, ReadBodyAsError(res) + return HealthcheckReport{}, codersdk.ReadBodyAsError(res) } var rpt HealthcheckReport return rpt, json.NewDecoder(res.Body).Decode(&rpt) } -func (c *Client) HealthSettings(ctx context.Context) (HealthSettings, error) { - res, err := c.Request(ctx, http.MethodGet, "/api/v2/debug/health/settings", nil) +func (c *HealthClient) HealthSettings(ctx context.Context) (HealthSettings, error) { + res, err := c.client.Request(ctx, http.MethodGet, "/api/v2/debug/health/settings", nil) if err != nil { return HealthSettings{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return HealthSettings{}, ReadBodyAsError(res) + return HealthSettings{}, codersdk.ReadBodyAsError(res) } var settings HealthSettings return settings, json.NewDecoder(res.Body).Decode(&settings) } -func (c *Client) PutHealthSettings(ctx context.Context, settings HealthSettings) error { - res, err := c.Request(ctx, http.MethodPut, "/api/v2/debug/health/settings", settings) +func (c *HealthClient) PutHealthSettings(ctx context.Context, settings HealthSettings) error { + res, err := c.client.Request(ctx, http.MethodPut, "/api/v2/debug/health/settings", settings) if err != nil { return err } @@ -81,7 +90,7 @@ func (c *Client) PutHealthSettings(ctx context.Context, settings HealthSettings) return xerrors.New("health settings not modified") } if res.StatusCode != http.StatusOK { - return ReadBodyAsError(res) + return codersdk.ReadBodyAsError(res) } return nil } @@ -199,8 +208,8 @@ type ProvisionerDaemonsReport struct { } type ProvisionerDaemonsReportItem struct { - ProvisionerDaemon `json:"provisioner_daemon"` - Warnings []health.Message `json:"warnings"` + codersdk.ProvisionerDaemon `json:"provisioner_daemon"` + Warnings []health.Message `json:"warnings"` } type WebsocketReport struct { @@ -222,5 +231,5 @@ type WorkspaceProxyReport struct { Dismissed bool `json:"dismissed"` Error *string `json:"error"` - WorkspaceProxies RegionsResponse[WorkspaceProxy] `json:"workspace_proxies"` + WorkspaceProxies codersdk.RegionsResponse[codersdk.WorkspaceProxy] `json:"workspace_proxies"` } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 7a8b2e5d3b..6eab57bb3c 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -3,28 +3,18 @@ package codersdk import ( "context" "encoding/json" - "errors" "fmt" "io" - "net" "net/http" "net/http/cookiejar" - "net/netip" - "strconv" "strings" - "sync" "time" "github.com/google/uuid" "golang.org/x/xerrors" "nhooyr.io/websocket" - "tailscale.com/tailcfg" - "cdr.dev/slog" "github.com/coder/coder/v2/coderd/tracing" - "github.com/coder/coder/v2/tailnet" - "github.com/coder/coder/v2/tailnet/proto" - "github.com/coder/retry" ) type WorkspaceAgentStatus string @@ -215,360 +205,28 @@ type DERPRegion struct { LatencyMilliseconds float64 `json:"latency_ms"` } -// WorkspaceAgentConnectionInfo returns required information for establishing -// a connection with a workspace. -// @typescript-ignore WorkspaceAgentConnectionInfo -type WorkspaceAgentConnectionInfo struct { - DERPMap *tailcfg.DERPMap `json:"derp_map"` - DERPForceWebSockets bool `json:"derp_force_websockets"` - DisableDirectConnections bool `json:"disable_direct_connections"` +type WorkspaceAgentLog struct { + ID int64 `json:"id"` + CreatedAt time.Time `json:"created_at" format:"date-time"` + Output string `json:"output"` + Level LogLevel `json:"level"` + SourceID uuid.UUID `json:"source_id" format:"uuid"` } -func (c *Client) WorkspaceAgentConnectionInfoGeneric(ctx context.Context) (WorkspaceAgentConnectionInfo, error) { - res, err := c.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/connection", nil) - if err != nil { - return WorkspaceAgentConnectionInfo{}, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return WorkspaceAgentConnectionInfo{}, ReadBodyAsError(res) - } +type AgentSubsystem string - var connInfo WorkspaceAgentConnectionInfo - return connInfo, json.NewDecoder(res.Body).Decode(&connInfo) -} +const ( + AgentSubsystemEnvbox AgentSubsystem = "envbox" + AgentSubsystemEnvbuilder AgentSubsystem = "envbuilder" + AgentSubsystemExectrace AgentSubsystem = "exectrace" +) -func (c *Client) WorkspaceAgentConnectionInfo(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentConnectionInfo, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/connection", agentID), nil) - if err != nil { - return WorkspaceAgentConnectionInfo{}, err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return WorkspaceAgentConnectionInfo{}, ReadBodyAsError(res) - } - - var connInfo WorkspaceAgentConnectionInfo - return connInfo, json.NewDecoder(res.Body).Decode(&connInfo) -} - -// @typescript-ignore DialWorkspaceAgentOptions -type DialWorkspaceAgentOptions struct { - Logger slog.Logger - // BlockEndpoints forced a direct connection through DERP. The Client may - // have DisableDirect set which will override this value. - BlockEndpoints bool -} - -func (c *Client) DialWorkspaceAgent(dialCtx context.Context, agentID uuid.UUID, options *DialWorkspaceAgentOptions) (agentConn *WorkspaceAgentConn, err error) { - if options == nil { - options = &DialWorkspaceAgentOptions{} - } - - connInfo, err := c.WorkspaceAgentConnectionInfo(dialCtx, agentID) - if err != nil { - return nil, xerrors.Errorf("get connection info: %w", err) - } - if connInfo.DisableDirectConnections { - options.BlockEndpoints = true - } - - ip := tailnet.IP() - var header http.Header - if headerTransport, ok := c.HTTPClient.Transport.(*HeaderTransport); ok { - header = headerTransport.Header - } - conn, err := tailnet.NewConn(&tailnet.Options{ - Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)}, - DERPMap: connInfo.DERPMap, - DERPHeader: &header, - DERPForceWebSockets: connInfo.DERPForceWebSockets, - Logger: options.Logger, - BlockEndpoints: c.DisableDirectConnections || options.BlockEndpoints, - }) - if err != nil { - return nil, xerrors.Errorf("create tailnet: %w", err) - } - defer func() { - if err != nil { - _ = conn.Close() - } - }() - - headers := make(http.Header) - tokenHeader := SessionTokenHeader - if c.SessionTokenHeader != "" { - tokenHeader = c.SessionTokenHeader - } - headers.Set(tokenHeader, c.SessionToken()) - - // New context, separate from dialCtx. We don't want to cancel the - // connection if dialCtx is canceled. - ctx, cancel := context.WithCancel(context.Background()) - defer func() { - if err != nil { - cancel() - } - }() - - coordinateURL, err := c.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/coordinate", agentID)) - if err != nil { - return nil, xerrors.Errorf("parse url: %w", err) - } - q := coordinateURL.Query() - q.Add("version", proto.CurrentVersion.String()) - coordinateURL.RawQuery = q.Encode() - - connector := runTailnetAPIConnector(ctx, options.Logger, - agentID, coordinateURL.String(), - &websocket.DialOptions{ - HTTPClient: c.HTTPClient, - HTTPHeader: headers, - // Need to disable compression to avoid a data-race. - CompressionMode: websocket.CompressionDisabled, - }, - conn, - ) - options.Logger.Debug(ctx, "running tailnet API v2+ connector") - - select { - case <-dialCtx.Done(): - return nil, xerrors.Errorf("timed out waiting for coordinator and derp map: %w", dialCtx.Err()) - case err = <-connector.connected: - if err != nil { - options.Logger.Error(ctx, "failed to connect to tailnet v2+ API", slog.Error(err)) - return nil, xerrors.Errorf("start connector: %w", err) - } - options.Logger.Debug(ctx, "connected to tailnet v2+ API") - } - - agentConn = NewWorkspaceAgentConn(conn, WorkspaceAgentConnOptions{ - AgentID: agentID, - CloseFunc: func() error { - cancel() - <-connector.closed - return conn.Close() - }, - }) - - if !agentConn.AwaitReachable(dialCtx) { - _ = agentConn.Close() - return nil, xerrors.Errorf("timed out waiting for agent to become reachable: %w", dialCtx.Err()) - } - - return agentConn, nil -} - -// tailnetConn is the subset of the tailnet.Conn methods that tailnetAPIConnector uses. It is -// included so that we can fake it in testing. -// -// @typescript-ignore tailnetConn -type tailnetConn interface { - tailnet.Coordinatee - SetDERPMap(derpMap *tailcfg.DERPMap) -} - -// tailnetAPIConnector dials the tailnet API (v2+) and then uses the API with a tailnet.Conn to -// -// 1) run the Coordinate API and pass node information back and forth -// 2) stream DERPMap updates and program the Conn -// -// These functions share the same websocket, and so are combined here so that if we hit a problem -// we tear the whole thing down and start over with a new websocket. -// -// @typescript-ignore tailnetAPIConnector -type tailnetAPIConnector struct { - // We keep track of two contexts: the main context from the caller, and a "graceful" context - // that we keep open slightly longer than the main context to give a chance to send the - // Disconnect message to the coordinator. That tells the coordinator that we really meant to - // disconnect instead of just losing network connectivity. - ctx context.Context - gracefulCtx context.Context - cancelGracefulCtx context.CancelFunc - - logger slog.Logger - - agentID uuid.UUID - coordinateURL string - dialOptions *websocket.DialOptions - conn tailnetConn - - connected chan error - isFirst bool - closed chan struct{} -} - -// runTailnetAPIConnector creates and runs a tailnetAPIConnector -func runTailnetAPIConnector( - ctx context.Context, logger slog.Logger, - agentID uuid.UUID, coordinateURL string, dialOptions *websocket.DialOptions, - conn tailnetConn, -) *tailnetAPIConnector { - tac := &tailnetAPIConnector{ - ctx: ctx, - logger: logger, - agentID: agentID, - coordinateURL: coordinateURL, - dialOptions: dialOptions, - conn: conn, - connected: make(chan error, 1), - closed: make(chan struct{}), - } - tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) - go tac.manageGracefulTimeout() - go tac.run() - return tac -} - -// manageGracefulTimeout allows the gracefulContext to last 1 second longer than the main context -// to allow a graceful disconnect. -func (tac *tailnetAPIConnector) manageGracefulTimeout() { - defer tac.cancelGracefulCtx() - <-tac.ctx.Done() - select { - case <-tac.closed: - case <-time.After(time.Second): - } -} - -func (tac *tailnetAPIConnector) run() { - tac.isFirst = true - defer close(tac.closed) - for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); { - tailnetClient, err := tac.dial() - if err != nil { - continue - } - tac.logger.Debug(tac.ctx, "obtained tailnet API v2+ client") - tac.coordinateAndDERPMap(tailnetClient) - tac.logger.Debug(tac.ctx, "tailnet API v2+ connection lost") - } -} - -func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) { - tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API") - // nolint:bodyclose - ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions) - if tac.isFirst { - if res != nil && res.StatusCode == http.StatusConflict { - err = ReadBodyAsError(res) - tac.connected <- err - return nil, err - } - tac.isFirst = false - close(tac.connected) - } - if err != nil { - if !errors.Is(err, context.Canceled) { - tac.logger.Error(tac.ctx, "failed to dial tailnet v2+ API", slog.Error(err)) - } - return nil, err - } - client, err := tailnet.NewDRPCClient( - websocket.NetConn(tac.gracefulCtx, ws, websocket.MessageBinary), - tac.logger, - ) - if err != nil { - tac.logger.Debug(tac.ctx, "failed to create DRPCClient", slog.Error(err)) - _ = ws.Close(websocket.StatusInternalError, "") - return nil, err - } - return client, err -} - -// coordinateAndDERPMap uses the provided client to coordinate and stream DERP Maps. It is combined -// into one function so that a problem with one tears down the other and triggers a retry (if -// appropriate). We multiplex both RPCs over the same websocket, so we want them to share the same -// fate. -func (tac *tailnetAPIConnector) coordinateAndDERPMap(client proto.DRPCTailnetClient) { - defer func() { - conn := client.DRPCConn() - closeErr := conn.Close() - if closeErr != nil && - !xerrors.Is(closeErr, io.EOF) && - !xerrors.Is(closeErr, context.Canceled) && - !xerrors.Is(closeErr, context.DeadlineExceeded) { - tac.logger.Error(tac.ctx, "error closing DRPC connection", slog.Error(closeErr)) - <-conn.Closed() - } - }() - wg := sync.WaitGroup{} - wg.Add(2) - go func() { - defer wg.Done() - tac.coordinate(client) - }() - go func() { - defer wg.Done() - dErr := tac.derpMap(client) - if dErr != nil && tac.ctx.Err() == nil { - // The main context is still active, meaning that we want the tailnet data plane to stay - // up, even though we hit some error getting DERP maps on the control plane. That means - // we do NOT want to gracefully disconnect on the coordinate() routine. So, we'll just - // close the underlying connection. This will trigger a retry of the control plane in - // run(). - client.DRPCConn().Close() - // Note that derpMap() logs it own errors, we don't bother here. - } - }() - wg.Wait() -} - -func (tac *tailnetAPIConnector) coordinate(client proto.DRPCTailnetClient) { - // we use the gracefulCtx here so that we'll have time to send the graceful disconnect - coord, err := client.Coordinate(tac.gracefulCtx) - if err != nil { - tac.logger.Error(tac.ctx, "failed to connect to Coordinate RPC", slog.Error(err)) - return - } - defer func() { - cErr := coord.Close() - if cErr != nil { - tac.logger.Debug(tac.ctx, "error closing Coordinate RPC", slog.Error(cErr)) - } - }() - coordination := tailnet.NewRemoteCoordination(tac.logger, coord, tac.conn, tac.agentID) - tac.logger.Debug(tac.ctx, "serving coordinator") - select { - case <-tac.ctx.Done(): - tac.logger.Debug(tac.ctx, "main context canceled; do graceful disconnect") - crdErr := coordination.Close() - if crdErr != nil { - tac.logger.Warn(tac.ctx, "failed to close remote coordination", slog.Error(err)) - } - case err = <-coordination.Error(): - if err != nil && - !xerrors.Is(err, io.EOF) && - !xerrors.Is(err, context.Canceled) && - !xerrors.Is(err, context.DeadlineExceeded) { - tac.logger.Error(tac.ctx, "remote coordination error", slog.Error(err)) - } - } -} - -func (tac *tailnetAPIConnector) derpMap(client proto.DRPCTailnetClient) error { - s, err := client.StreamDERPMaps(tac.ctx, &proto.StreamDERPMapsRequest{}) - if err != nil { - return xerrors.Errorf("failed to connect to StreamDERPMaps RPC: %w", err) - } - defer func() { - cErr := s.Close() - if cErr != nil { - tac.logger.Debug(tac.ctx, "error closing StreamDERPMaps RPC", slog.Error(cErr)) - } - }() - for { - dmp, err := s.Recv() - if err != nil { - if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) { - return nil - } - tac.logger.Error(tac.ctx, "error receiving DERP Map", slog.Error(err)) - return err - } - tac.logger.Debug(tac.ctx, "got new DERP Map", slog.F("derp_map", dmp)) - dm := tailnet.DERPMapFromProto(dmp) - tac.conn.SetDERPMap(dm) +func (s AgentSubsystem) Valid() bool { + switch s { + case AgentSubsystemEnvbox, AgentSubsystemEnvbuilder, AgentSubsystemExectrace: + return true + default: + return false } } @@ -702,66 +360,18 @@ func (c *Client) IssueReconnectingPTYSignedToken(ctx context.Context, req IssueR return resp, json.NewDecoder(res.Body).Decode(&resp) } -// @typescript-ignore:WorkspaceAgentReconnectingPTYOpts -type WorkspaceAgentReconnectingPTYOpts struct { - AgentID uuid.UUID - Reconnect uuid.UUID - Width uint16 - Height uint16 - Command string - - // SignedToken is an optional signed token from the - // issue-reconnecting-pty-signed-token endpoint. If set, the session token - // on the client will not be sent. - SignedToken string +type WorkspaceAgentListeningPortsResponse struct { + // If there are no ports in the list, nothing should be displayed in the UI. + // There must not be a "no ports available" message or anything similar, as + // there will always be no ports displayed on platforms where our port + // detection logic is unsupported. + Ports []WorkspaceAgentListeningPort `json:"ports"` } -// WorkspaceAgentReconnectingPTY spawns a PTY that reconnects using the token provided. -// It communicates using `agent.ReconnectingPTYRequest` marshaled as JSON. -// Responses are PTY output that can be rendered. -func (c *Client) WorkspaceAgentReconnectingPTY(ctx context.Context, opts WorkspaceAgentReconnectingPTYOpts) (net.Conn, error) { - serverURL, err := c.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/pty", opts.AgentID)) - if err != nil { - return nil, xerrors.Errorf("parse url: %w", err) - } - q := serverURL.Query() - q.Set("reconnect", opts.Reconnect.String()) - q.Set("width", strconv.Itoa(int(opts.Width))) - q.Set("height", strconv.Itoa(int(opts.Height))) - q.Set("command", opts.Command) - // If we're using a signed token, set the query parameter. - if opts.SignedToken != "" { - q.Set(SignedAppTokenQueryParameter, opts.SignedToken) - } - serverURL.RawQuery = q.Encode() - - // If we're not using a signed token, we need to set the session token as a - // cookie. - httpClient := c.HTTPClient - if opts.SignedToken == "" { - jar, err := cookiejar.New(nil) - if err != nil { - return nil, xerrors.Errorf("create cookie jar: %w", err) - } - jar.SetCookies(serverURL, []*http.Cookie{{ - Name: SessionTokenCookie, - Value: c.SessionToken(), - }}) - httpClient = &http.Client{ - Jar: jar, - Transport: c.HTTPClient.Transport, - } - } - conn, res, err := websocket.Dial(ctx, serverURL.String(), &websocket.DialOptions{ - HTTPClient: httpClient, - }) - if err != nil { - if res == nil { - return nil, err - } - return nil, ReadBodyAsError(res) - } - return websocket.NetConn(context.Background(), conn, websocket.MessageBinary), nil +type WorkspaceAgentListeningPort struct { + ProcessName string `json:"process_name"` // may be empty + Network string `json:"network"` // only "tcp" at the moment + Port uint16 `json:"port"` } // WorkspaceAgentListeningPorts returns a list of ports that are currently being @@ -869,28 +479,3 @@ func (c *Client) WorkspaceAgentLogsAfter(ctx context.Context, agentID uuid.UUID, return nil }), nil } - -type WorkspaceAgentLog struct { - ID int64 `json:"id"` - CreatedAt time.Time `json:"created_at" format:"date-time"` - Output string `json:"output"` - Level LogLevel `json:"level"` - SourceID uuid.UUID `json:"source_id" format:"uuid"` -} - -type AgentSubsystem string - -const ( - AgentSubsystemEnvbox AgentSubsystem = "envbox" - AgentSubsystemEnvbuilder AgentSubsystem = "envbuilder" - AgentSubsystemExectrace AgentSubsystem = "exectrace" -) - -func (s AgentSubsystem) Valid() bool { - switch s { - case AgentSubsystemEnvbox, AgentSubsystemEnvbuilder, AgentSubsystemExectrace: - return true - default: - return false - } -} diff --git a/codersdk/workspaceagentconn.go b/codersdk/workspacesdk/agentconn.go similarity index 58% rename from codersdk/workspaceagentconn.go rename to codersdk/workspacesdk/agentconn.go index cf85ee07de..6700f5d935 100644 --- a/codersdk/workspaceagentconn.go +++ b/codersdk/workspacesdk/agentconn.go @@ -1,4 +1,4 @@ -package codersdk +package workspacesdk import ( "context" @@ -9,9 +9,7 @@ import ( "net" "net/http" "net/netip" - "os" "strconv" - "strings" "time" "github.com/google/uuid" @@ -23,141 +21,40 @@ import ( "tailscale.com/net/speedtest" "github.com/coder/coder/v2/coderd/tracing" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/tailnet" ) -// WorkspaceAgentIP is a static IPv6 address with the Tailscale prefix that is used to route -// connections from clients to this node. A dynamic address is not required because a Tailnet -// client only dials a single agent at a time. -// -// Deprecated: use tailnet.IP() instead. This is kept for backwards -// compatibility with outdated CLI clients and Workspace Proxies that dial it. -// See: https://github.com/coder/coder/issues/11819 -var WorkspaceAgentIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4") - -var ErrSkipClose = xerrors.New("skip tailnet close") - -const ( - WorkspaceAgentSSHPort = tailnet.WorkspaceAgentSSHPort - WorkspaceAgentReconnectingPTYPort = tailnet.WorkspaceAgentReconnectingPTYPort - WorkspaceAgentSpeedtestPort = tailnet.WorkspaceAgentSpeedtestPort - // WorkspaceAgentHTTPAPIServerPort serves a HTTP server with endpoints for e.g. - // gathering agent statistics. - WorkspaceAgentHTTPAPIServerPort = 4 - - // WorkspaceAgentMinimumListeningPort is the minimum port that the listening-ports - // endpoint will return to the client, and the minimum port that is accepted - // by the proxy applications endpoint. Coder consumes ports 1-4 at the - // moment, and we reserve some extra ports for future use. Port 9 and up are - // available for the user. - // - // This is not enforced in the CLI intentionally as we don't really care - // *that* much. The user could bypass this in the CLI by using SSH instead - // anyways. - WorkspaceAgentMinimumListeningPort = 9 -) - -// WorkspaceAgentIgnoredListeningPorts contains a list of ports to ignore when looking for -// running applications inside a workspace. We want to ignore non-HTTP servers, -// so we pre-populate this list with common ports that are not HTTP servers. -// -// This is implemented as a map for fast lookup. -var WorkspaceAgentIgnoredListeningPorts = map[uint16]struct{}{ - 0: {}, - // Ports 1-8 are reserved for future use by the Coder agent. - 1: {}, - 2: {}, - 3: {}, - 4: {}, - 5: {}, - 6: {}, - 7: {}, - 8: {}, - // ftp - 20: {}, - 21: {}, - // ssh - 22: {}, - // telnet - 23: {}, - // smtp - 25: {}, - // dns over TCP - 53: {}, - // pop3 - 110: {}, - // imap - 143: {}, - // bgp - 179: {}, - // ldap - 389: {}, - 636: {}, - // smtps - 465: {}, - // smtp - 587: {}, - // ftps - 989: {}, - 990: {}, - // imaps - 993: {}, - // pop3s - 995: {}, - // mysql - 3306: {}, - // rdp - 3389: {}, - // postgres - 5432: {}, - // mongodb - 27017: {}, - 27018: {}, - 27019: {}, - 28017: {}, -} - -func init() { - if !strings.HasSuffix(os.Args[0], ".test") { - return - } - // Add a thousand more ports to the ignore list during tests so it's easier - // to find an available port. - for i := 63000; i < 64000; i++ { - WorkspaceAgentIgnoredListeningPorts[uint16(i)] = struct{}{} - } -} - -// NewWorkspaceAgentConn creates a new WorkspaceAgentConn. `conn` may be unique +// NewAgentConn creates a new WorkspaceAgentConn. `conn` may be unique // to the WorkspaceAgentConn, or it may be shared in the case of coderd. If the // conn is shared and closing it is undesirable, you may return ErrNoClose from // opts.CloseFunc. This will ensure the underlying conn is not closed. -func NewWorkspaceAgentConn(conn *tailnet.Conn, opts WorkspaceAgentConnOptions) *WorkspaceAgentConn { - return &WorkspaceAgentConn{ +func NewAgentConn(conn *tailnet.Conn, opts AgentConnOptions) *AgentConn { + return &AgentConn{ Conn: conn, opts: opts, } } -// WorkspaceAgentConn represents a connection to a workspace agent. -// @typescript-ignore WorkspaceAgentConn -type WorkspaceAgentConn struct { +// AgentConn represents a connection to a workspace agent. +// @typescript-ignore AgentConn +type AgentConn struct { *tailnet.Conn - opts WorkspaceAgentConnOptions + opts AgentConnOptions } -// @typescript-ignore WorkspaceAgentConnOptions -type WorkspaceAgentConnOptions struct { +// @typescript-ignore AgentConnOptions +type AgentConnOptions struct { AgentID uuid.UUID CloseFunc func() error } -func (c *WorkspaceAgentConn) agentAddress() netip.Addr { +func (c *AgentConn) agentAddress() netip.Addr { return tailnet.IPFromUUID(c.opts.AgentID) } // AwaitReachable waits for the agent to be reachable. -func (c *WorkspaceAgentConn) AwaitReachable(ctx context.Context) bool { +func (c *AgentConn) AwaitReachable(ctx context.Context) bool { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -166,7 +63,7 @@ func (c *WorkspaceAgentConn) AwaitReachable(ctx context.Context) bool { // Ping pings the agent and returns the round-trip time. // The bool returns true if the ping was made P2P. -func (c *WorkspaceAgentConn) Ping(ctx context.Context) (time.Duration, bool, *ipnstate.PingResult, error) { +func (c *AgentConn) Ping(ctx context.Context) (time.Duration, bool, *ipnstate.PingResult, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -174,7 +71,7 @@ func (c *WorkspaceAgentConn) Ping(ctx context.Context) (time.Duration, bool, *ip } // Close ends the connection to the workspace agent. -func (c *WorkspaceAgentConn) Close() error { +func (c *AgentConn) Close() error { var cerr error if c.opts.CloseFunc != nil { cerr = c.opts.CloseFunc() @@ -188,9 +85,9 @@ func (c *WorkspaceAgentConn) Close() error { return c.Conn.Close() } -// WorkspaceAgentReconnectingPTYInit initializes a new reconnecting PTY session. -// @typescript-ignore WorkspaceAgentReconnectingPTYInit -type WorkspaceAgentReconnectingPTYInit struct { +// AgentReconnectingPTYInit initializes a new reconnecting PTY session. +// @typescript-ignore AgentReconnectingPTYInit +type AgentReconnectingPTYInit struct { ID uuid.UUID Height uint16 Width uint16 @@ -209,7 +106,7 @@ type ReconnectingPTYRequest struct { // ReconnectingPTY spawns a new reconnecting terminal session. // `ReconnectingPTYRequest` should be JSON marshaled and written to the returned net.Conn. // Raw terminal output will be read from the returned net.Conn. -func (c *WorkspaceAgentConn) ReconnectingPTY(ctx context.Context, id uuid.UUID, height, width uint16, command string) (net.Conn, error) { +func (c *AgentConn) ReconnectingPTY(ctx context.Context, id uuid.UUID, height, width uint16, command string) (net.Conn, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -217,11 +114,11 @@ func (c *WorkspaceAgentConn) ReconnectingPTY(ctx context.Context, id uuid.UUID, return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err()) } - conn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), WorkspaceAgentReconnectingPTYPort)) + conn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentReconnectingPTYPort)) if err != nil { return nil, err } - data, err := json.Marshal(WorkspaceAgentReconnectingPTYInit{ + data, err := json.Marshal(AgentReconnectingPTYInit{ ID: id, Height: height, Width: width, @@ -244,7 +141,7 @@ func (c *WorkspaceAgentConn) ReconnectingPTY(ctx context.Context, id uuid.UUID, // SSH pipes the SSH protocol over the returned net.Conn. // This connects to the built-in SSH server in the workspace agent. -func (c *WorkspaceAgentConn) SSH(ctx context.Context) (*gonet.TCPConn, error) { +func (c *AgentConn) SSH(ctx context.Context) (*gonet.TCPConn, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -252,12 +149,12 @@ func (c *WorkspaceAgentConn) SSH(ctx context.Context) (*gonet.TCPConn, error) { return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err()) } - return c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), WorkspaceAgentSSHPort)) + return c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSSHPort)) } // SSHClient calls SSH to create a client that uses a weak cipher // to improve throughput. -func (c *WorkspaceAgentConn) SSHClient(ctx context.Context) (*ssh.Client, error) { +func (c *AgentConn) SSHClient(ctx context.Context) (*ssh.Client, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -280,7 +177,7 @@ func (c *WorkspaceAgentConn) SSHClient(ctx context.Context) (*ssh.Client, error) } // Speedtest runs a speedtest against the workspace agent. -func (c *WorkspaceAgentConn) Speedtest(ctx context.Context, direction speedtest.Direction, duration time.Duration) ([]speedtest.Result, error) { +func (c *AgentConn) Speedtest(ctx context.Context, direction speedtest.Direction, duration time.Duration) ([]speedtest.Result, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -288,7 +185,7 @@ func (c *WorkspaceAgentConn) Speedtest(ctx context.Context, direction speedtest. return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err()) } - speedConn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), WorkspaceAgentSpeedtestPort)) + speedConn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.agentAddress(), AgentSpeedtestPort)) if err != nil { return nil, xerrors.Errorf("dial speedtest: %w", err) } @@ -303,7 +200,7 @@ func (c *WorkspaceAgentConn) Speedtest(ctx context.Context, direction speedtest. // DialContext dials the address provided in the workspace agent. // The network must be "tcp" or "udp". -func (c *WorkspaceAgentConn) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { +func (c *AgentConn) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -325,39 +222,25 @@ func (c *WorkspaceAgentConn) DialContext(ctx context.Context, network string, ad } } -type WorkspaceAgentListeningPortsResponse struct { - // If there are no ports in the list, nothing should be displayed in the UI. - // There must not be a "no ports available" message or anything similar, as - // there will always be no ports displayed on platforms where our port - // detection logic is unsupported. - Ports []WorkspaceAgentListeningPort `json:"ports"` -} - -type WorkspaceAgentListeningPort struct { - ProcessName string `json:"process_name"` // may be empty - Network string `json:"network"` // only "tcp" at the moment - Port uint16 `json:"port"` -} - // ListeningPorts lists the ports that are currently in use by the workspace. -func (c *WorkspaceAgentConn) ListeningPorts(ctx context.Context) (WorkspaceAgentListeningPortsResponse, error) { +func (c *AgentConn) ListeningPorts(ctx context.Context) (codersdk.WorkspaceAgentListeningPortsResponse, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() res, err := c.apiRequest(ctx, http.MethodGet, "/api/v0/listening-ports", nil) if err != nil { - return WorkspaceAgentListeningPortsResponse{}, xerrors.Errorf("do request: %w", err) + return codersdk.WorkspaceAgentListeningPortsResponse{}, xerrors.Errorf("do request: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return WorkspaceAgentListeningPortsResponse{}, ReadBodyAsError(res) + return codersdk.WorkspaceAgentListeningPortsResponse{}, codersdk.ReadBodyAsError(res) } - var resp WorkspaceAgentListeningPortsResponse + var resp codersdk.WorkspaceAgentListeningPortsResponse return resp, json.NewDecoder(res.Body).Decode(&resp) } // DebugMagicsock makes a request to the workspace agent's magicsock debug endpoint. -func (c *WorkspaceAgentConn) DebugMagicsock(ctx context.Context) ([]byte, error) { +func (c *AgentConn) DebugMagicsock(ctx context.Context) ([]byte, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() res, err := c.apiRequest(ctx, http.MethodGet, "/debug/magicsock", nil) @@ -365,7 +248,7 @@ func (c *WorkspaceAgentConn) DebugMagicsock(ctx context.Context) ([]byte, error) return nil, xerrors.Errorf("do request: %w", err) } if res.StatusCode != http.StatusOK { - return nil, ReadBodyAsError(res) + return nil, codersdk.ReadBodyAsError(res) } defer res.Body.Close() bs, err := io.ReadAll(res.Body) @@ -377,7 +260,7 @@ func (c *WorkspaceAgentConn) DebugMagicsock(ctx context.Context) ([]byte, error) // DebugManifest returns the agent's in-memory manifest. Unfortunately this must // be returns as a []byte to avoid an import cycle. -func (c *WorkspaceAgentConn) DebugManifest(ctx context.Context) ([]byte, error) { +func (c *AgentConn) DebugManifest(ctx context.Context) ([]byte, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() res, err := c.apiRequest(ctx, http.MethodGet, "/debug/manifest", nil) @@ -386,7 +269,7 @@ func (c *WorkspaceAgentConn) DebugManifest(ctx context.Context) ([]byte, error) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return nil, ReadBodyAsError(res) + return nil, codersdk.ReadBodyAsError(res) } bs, err := io.ReadAll(res.Body) if err != nil { @@ -396,7 +279,7 @@ func (c *WorkspaceAgentConn) DebugManifest(ctx context.Context) ([]byte, error) } // DebugLogs returns up to the last 10MB of `/tmp/coder-agent.log` -func (c *WorkspaceAgentConn) DebugLogs(ctx context.Context) ([]byte, error) { +func (c *AgentConn) DebugLogs(ctx context.Context) ([]byte, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() res, err := c.apiRequest(ctx, http.MethodGet, "/debug/logs", nil) @@ -405,7 +288,7 @@ func (c *WorkspaceAgentConn) DebugLogs(ctx context.Context) ([]byte, error) { } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return nil, ReadBodyAsError(res) + return nil, codersdk.ReadBodyAsError(res) } bs, err := io.ReadAll(res.Body) if err != nil { @@ -415,7 +298,7 @@ func (c *WorkspaceAgentConn) DebugLogs(ctx context.Context) ([]byte, error) { } // PrometheusMetrics returns a response from the agent's prometheus metrics endpoint -func (c *WorkspaceAgentConn) PrometheusMetrics(ctx context.Context) ([]byte, error) { +func (c *AgentConn) PrometheusMetrics(ctx context.Context) ([]byte, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() res, err := c.apiRequest(ctx, http.MethodGet, "/debug/prometheus", nil) @@ -424,7 +307,7 @@ func (c *WorkspaceAgentConn) PrometheusMetrics(ctx context.Context) ([]byte, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return nil, ReadBodyAsError(res) + return nil, codersdk.ReadBodyAsError(res) } bs, err := io.ReadAll(res.Body) if err != nil { @@ -434,11 +317,11 @@ func (c *WorkspaceAgentConn) PrometheusMetrics(ctx context.Context) ([]byte, err } // apiRequest makes a request to the workspace agent's HTTP API server. -func (c *WorkspaceAgentConn) apiRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) { +func (c *AgentConn) apiRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) { ctx, span := tracing.StartSpan(ctx) defer span.End() - host := net.JoinHostPort(c.agentAddress().String(), strconv.Itoa(WorkspaceAgentHTTPAPIServerPort)) + host := net.JoinHostPort(c.agentAddress().String(), strconv.Itoa(AgentHTTPAPIServerPort)) url := fmt.Sprintf("http://%s%s", host, path) req, err := http.NewRequestWithContext(ctx, method, url, body) @@ -451,7 +334,7 @@ func (c *WorkspaceAgentConn) apiRequest(ctx context.Context, method, path string // apiClient returns an HTTP client that can be used to make // requests to the workspace agent's HTTP API server. -func (c *WorkspaceAgentConn) apiClient() *http.Client { +func (c *AgentConn) apiClient() *http.Client { return &http.Client{ Transport: &http.Transport{ // Disable keep alives as we're usually only making a single @@ -468,7 +351,7 @@ func (c *WorkspaceAgentConn) apiClient() *http.Client { } // Verify that the port is TailnetStatisticsPort. - if port != strconv.Itoa(WorkspaceAgentHTTPAPIServerPort) { + if port != strconv.Itoa(AgentHTTPAPIServerPort) { return nil, xerrors.Errorf("request %q does not appear to be for http api", addr) } @@ -481,7 +364,7 @@ func (c *WorkspaceAgentConn) apiClient() *http.Client { return nil, xerrors.Errorf("parse host addr: %w", err) } - conn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(ipAddr, WorkspaceAgentHTTPAPIServerPort)) + conn, err := c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(ipAddr, AgentHTTPAPIServerPort)) if err != nil { return nil, xerrors.Errorf("dial http api: %w", err) } @@ -492,6 +375,6 @@ func (c *WorkspaceAgentConn) apiClient() *http.Client { } } -func (c *WorkspaceAgentConn) GetPeerDiagnostics() tailnet.PeerDiagnostics { +func (c *AgentConn) GetPeerDiagnostics() tailnet.PeerDiagnostics { return c.Conn.GetPeerDiagnostics(c.opts.AgentID) } diff --git a/codersdk/workspacesdk/connector.go b/codersdk/workspacesdk/connector.go new file mode 100644 index 0000000000..5c1d9e600a --- /dev/null +++ b/codersdk/workspacesdk/connector.go @@ -0,0 +1,234 @@ +package workspacesdk + +import ( + "context" + "errors" + "io" + "net/http" + "sync" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + "nhooyr.io/websocket" + "tailscale.com/tailcfg" + + "cdr.dev/slog" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/tailnet" + "github.com/coder/coder/v2/tailnet/proto" + "github.com/coder/retry" +) + +// tailnetConn is the subset of the tailnet.Conn methods that tailnetAPIConnector uses. It is +// included so that we can fake it in testing. +// +// @typescript-ignore tailnetConn +type tailnetConn interface { + tailnet.Coordinatee + SetDERPMap(derpMap *tailcfg.DERPMap) +} + +// tailnetAPIConnector dials the tailnet API (v2+) and then uses the API with a tailnet.Conn to +// +// 1) run the Coordinate API and pass node information back and forth +// 2) stream DERPMap updates and program the Conn +// +// These functions share the same websocket, and so are combined here so that if we hit a problem +// we tear the whole thing down and start over with a new websocket. +// +// @typescript-ignore tailnetAPIConnector +type tailnetAPIConnector struct { + // We keep track of two contexts: the main context from the caller, and a "graceful" context + // that we keep open slightly longer than the main context to give a chance to send the + // Disconnect message to the coordinator. That tells the coordinator that we really meant to + // disconnect instead of just losing network connectivity. + ctx context.Context + gracefulCtx context.Context + cancelGracefulCtx context.CancelFunc + + logger slog.Logger + + agentID uuid.UUID + coordinateURL string + dialOptions *websocket.DialOptions + conn tailnetConn + + connected chan error + isFirst bool + closed chan struct{} +} + +// runTailnetAPIConnector creates and runs a tailnetAPIConnector +func runTailnetAPIConnector( + ctx context.Context, logger slog.Logger, + agentID uuid.UUID, coordinateURL string, dialOptions *websocket.DialOptions, + conn tailnetConn, +) *tailnetAPIConnector { + tac := &tailnetAPIConnector{ + ctx: ctx, + logger: logger, + agentID: agentID, + coordinateURL: coordinateURL, + dialOptions: dialOptions, + conn: conn, + connected: make(chan error, 1), + closed: make(chan struct{}), + } + tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background()) + go tac.manageGracefulTimeout() + go tac.run() + return tac +} + +// manageGracefulTimeout allows the gracefulContext to last 1 second longer than the main context +// to allow a graceful disconnect. +func (tac *tailnetAPIConnector) manageGracefulTimeout() { + defer tac.cancelGracefulCtx() + <-tac.ctx.Done() + select { + case <-tac.closed: + case <-time.After(time.Second): + } +} + +func (tac *tailnetAPIConnector) run() { + tac.isFirst = true + defer close(tac.closed) + for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); { + tailnetClient, err := tac.dial() + if err != nil { + continue + } + tac.logger.Debug(tac.ctx, "obtained tailnet API v2+ client") + tac.coordinateAndDERPMap(tailnetClient) + tac.logger.Debug(tac.ctx, "tailnet API v2+ connection lost") + } +} + +func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) { + tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API") + // nolint:bodyclose + ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions) + if tac.isFirst { + if res != nil && res.StatusCode == http.StatusConflict { + err = codersdk.ReadBodyAsError(res) + tac.connected <- err + return nil, err + } + tac.isFirst = false + close(tac.connected) + } + if err != nil { + if !errors.Is(err, context.Canceled) { + tac.logger.Error(tac.ctx, "failed to dial tailnet v2+ API", slog.Error(err)) + } + return nil, err + } + client, err := tailnet.NewDRPCClient( + websocket.NetConn(tac.gracefulCtx, ws, websocket.MessageBinary), + tac.logger, + ) + if err != nil { + tac.logger.Debug(tac.ctx, "failed to create DRPCClient", slog.Error(err)) + _ = ws.Close(websocket.StatusInternalError, "") + return nil, err + } + return client, err +} + +// coordinateAndDERPMap uses the provided client to coordinate and stream DERP Maps. It is combined +// into one function so that a problem with one tears down the other and triggers a retry (if +// appropriate). We multiplex both RPCs over the same websocket, so we want them to share the same +// fate. +func (tac *tailnetAPIConnector) coordinateAndDERPMap(client proto.DRPCTailnetClient) { + defer func() { + conn := client.DRPCConn() + closeErr := conn.Close() + if closeErr != nil && + !xerrors.Is(closeErr, io.EOF) && + !xerrors.Is(closeErr, context.Canceled) && + !xerrors.Is(closeErr, context.DeadlineExceeded) { + tac.logger.Error(tac.ctx, "error closing DRPC connection", slog.Error(closeErr)) + <-conn.Closed() + } + }() + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + defer wg.Done() + tac.coordinate(client) + }() + go func() { + defer wg.Done() + dErr := tac.derpMap(client) + if dErr != nil && tac.ctx.Err() == nil { + // The main context is still active, meaning that we want the tailnet data plane to stay + // up, even though we hit some error getting DERP maps on the control plane. That means + // we do NOT want to gracefully disconnect on the coordinate() routine. So, we'll just + // close the underlying connection. This will trigger a retry of the control plane in + // run(). + client.DRPCConn().Close() + // Note that derpMap() logs it own errors, we don't bother here. + } + }() + wg.Wait() +} + +func (tac *tailnetAPIConnector) coordinate(client proto.DRPCTailnetClient) { + // we use the gracefulCtx here so that we'll have time to send the graceful disconnect + coord, err := client.Coordinate(tac.gracefulCtx) + if err != nil { + tac.logger.Error(tac.ctx, "failed to connect to Coordinate RPC", slog.Error(err)) + return + } + defer func() { + cErr := coord.Close() + if cErr != nil { + tac.logger.Debug(tac.ctx, "error closing Coordinate RPC", slog.Error(cErr)) + } + }() + coordination := tailnet.NewRemoteCoordination(tac.logger, coord, tac.conn, tac.agentID) + tac.logger.Debug(tac.ctx, "serving coordinator") + select { + case <-tac.ctx.Done(): + tac.logger.Debug(tac.ctx, "main context canceled; do graceful disconnect") + crdErr := coordination.Close() + if crdErr != nil { + tac.logger.Warn(tac.ctx, "failed to close remote coordination", slog.Error(err)) + } + case err = <-coordination.Error(): + if err != nil && + !xerrors.Is(err, io.EOF) && + !xerrors.Is(err, context.Canceled) && + !xerrors.Is(err, context.DeadlineExceeded) { + tac.logger.Error(tac.ctx, "remote coordination error", slog.Error(err)) + } + } +} + +func (tac *tailnetAPIConnector) derpMap(client proto.DRPCTailnetClient) error { + s, err := client.StreamDERPMaps(tac.ctx, &proto.StreamDERPMapsRequest{}) + if err != nil { + return xerrors.Errorf("failed to connect to StreamDERPMaps RPC: %w", err) + } + defer func() { + cErr := s.Close() + if cErr != nil { + tac.logger.Debug(tac.ctx, "error closing StreamDERPMaps RPC", slog.Error(cErr)) + } + }() + for { + dmp, err := s.Recv() + if err != nil { + if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) { + return nil + } + tac.logger.Error(tac.ctx, "error receiving DERP Map", slog.Error(err)) + return err + } + tac.logger.Debug(tac.ctx, "got new DERP Map", slog.F("derp_map", dmp)) + dm := tailnet.DERPMapFromProto(dmp) + tac.conn.SetDERPMap(dm) + } +} diff --git a/codersdk/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go new file mode 100644 index 0000000000..8af29f8f76 --- /dev/null +++ b/codersdk/workspacesdk/workspacesdk.go @@ -0,0 +1,341 @@ +package workspacesdk + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "net/http/cookiejar" + "net/netip" + "os" + "strconv" + "strings" + + "github.com/google/uuid" + "golang.org/x/xerrors" + "nhooyr.io/websocket" + "tailscale.com/tailcfg" + + "cdr.dev/slog" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/tailnet" + "github.com/coder/coder/v2/tailnet/proto" +) + +// AgentIP is a static IPv6 address with the Tailscale prefix that is used to route +// connections from clients to this node. A dynamic address is not required because a Tailnet +// client only dials a single agent at a time. +// +// Deprecated: use tailnet.IP() instead. This is kept for backwards +// compatibility with outdated CLI clients and Workspace Proxies that dial it. +// See: https://github.com/coder/coder/issues/11819 +var AgentIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4") + +var ErrSkipClose = xerrors.New("skip tailnet close") + +const ( + AgentSSHPort = tailnet.WorkspaceAgentSSHPort + AgentReconnectingPTYPort = tailnet.WorkspaceAgentReconnectingPTYPort + AgentSpeedtestPort = tailnet.WorkspaceAgentSpeedtestPort + // AgentHTTPAPIServerPort serves a HTTP server with endpoints for e.g. + // gathering agent statistics. + AgentHTTPAPIServerPort = 4 + + // AgentMinimumListeningPort is the minimum port that the listening-ports + // endpoint will return to the client, and the minimum port that is accepted + // by the proxy applications endpoint. Coder consumes ports 1-4 at the + // moment, and we reserve some extra ports for future use. Port 9 and up are + // available for the user. + // + // This is not enforced in the CLI intentionally as we don't really care + // *that* much. The user could bypass this in the CLI by using SSH instead + // anyways. + AgentMinimumListeningPort = 9 +) + +// AgentIgnoredListeningPorts contains a list of ports to ignore when looking for +// running applications inside a workspace. We want to ignore non-HTTP servers, +// so we pre-populate this list with common ports that are not HTTP servers. +// +// This is implemented as a map for fast lookup. +var AgentIgnoredListeningPorts = map[uint16]struct{}{ + 0: {}, + // Ports 1-8 are reserved for future use by the Coder agent. + 1: {}, + 2: {}, + 3: {}, + 4: {}, + 5: {}, + 6: {}, + 7: {}, + 8: {}, + // ftp + 20: {}, + 21: {}, + // ssh + 22: {}, + // telnet + 23: {}, + // smtp + 25: {}, + // dns over TCP + 53: {}, + // pop3 + 110: {}, + // imap + 143: {}, + // bgp + 179: {}, + // ldap + 389: {}, + 636: {}, + // smtps + 465: {}, + // smtp + 587: {}, + // ftps + 989: {}, + 990: {}, + // imaps + 993: {}, + // pop3s + 995: {}, + // mysql + 3306: {}, + // rdp + 3389: {}, + // postgres + 5432: {}, + // mongodb + 27017: {}, + 27018: {}, + 27019: {}, + 28017: {}, +} + +func init() { + if !strings.HasSuffix(os.Args[0], ".test") { + return + } + // Add a thousand more ports to the ignore list during tests so it's easier + // to find an available port. + for i := 63000; i < 64000; i++ { + AgentIgnoredListeningPorts[uint16(i)] = struct{}{} + } +} + +type Client struct { + client *codersdk.Client +} + +func New(c *codersdk.Client) *Client { + return &Client{client: c} +} + +// AgentConnectionInfo returns required information for establishing +// a connection with a workspace. +// @typescript-ignore AgentConnectionInfo +type AgentConnectionInfo struct { + DERPMap *tailcfg.DERPMap `json:"derp_map"` + DERPForceWebSockets bool `json:"derp_force_websockets"` + DisableDirectConnections bool `json:"disable_direct_connections"` +} + +func (c *Client) AgentConnectionInfoGeneric(ctx context.Context) (AgentConnectionInfo, error) { + res, err := c.client.Request(ctx, http.MethodGet, "/api/v2/workspaceagents/connection", nil) + if err != nil { + return AgentConnectionInfo{}, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return AgentConnectionInfo{}, codersdk.ReadBodyAsError(res) + } + + var connInfo AgentConnectionInfo + return connInfo, json.NewDecoder(res.Body).Decode(&connInfo) +} + +func (c *Client) AgentConnectionInfo(ctx context.Context, agentID uuid.UUID) (AgentConnectionInfo, error) { + res, err := c.client.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/connection", agentID), nil) + if err != nil { + return AgentConnectionInfo{}, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return AgentConnectionInfo{}, codersdk.ReadBodyAsError(res) + } + + var connInfo AgentConnectionInfo + return connInfo, json.NewDecoder(res.Body).Decode(&connInfo) +} + +// @typescript-ignore DialAgentOptions +type DialAgentOptions struct { + Logger slog.Logger + // BlockEndpoints forced a direct connection through DERP. The Client may + // have DisableDirect set which will override this value. + BlockEndpoints bool +} + +func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *DialAgentOptions) (agentConn *AgentConn, err error) { + if options == nil { + options = &DialAgentOptions{} + } + + connInfo, err := c.AgentConnectionInfo(dialCtx, agentID) + if err != nil { + return nil, xerrors.Errorf("get connection info: %w", err) + } + if connInfo.DisableDirectConnections { + options.BlockEndpoints = true + } + + ip := tailnet.IP() + var header http.Header + if headerTransport, ok := c.client.HTTPClient.Transport.(*codersdk.HeaderTransport); ok { + header = headerTransport.Header + } + conn, err := tailnet.NewConn(&tailnet.Options{ + Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)}, + DERPMap: connInfo.DERPMap, + DERPHeader: &header, + DERPForceWebSockets: connInfo.DERPForceWebSockets, + Logger: options.Logger, + BlockEndpoints: c.client.DisableDirectConnections || options.BlockEndpoints, + }) + if err != nil { + return nil, xerrors.Errorf("create tailnet: %w", err) + } + defer func() { + if err != nil { + _ = conn.Close() + } + }() + + headers := make(http.Header) + tokenHeader := codersdk.SessionTokenHeader + if c.client.SessionTokenHeader != "" { + tokenHeader = c.client.SessionTokenHeader + } + headers.Set(tokenHeader, c.client.SessionToken()) + + // New context, separate from dialCtx. We don't want to cancel the + // connection if dialCtx is canceled. + ctx, cancel := context.WithCancel(context.Background()) + defer func() { + if err != nil { + cancel() + } + }() + + coordinateURL, err := c.client.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/coordinate", agentID)) + if err != nil { + return nil, xerrors.Errorf("parse url: %w", err) + } + q := coordinateURL.Query() + q.Add("version", proto.CurrentVersion.String()) + coordinateURL.RawQuery = q.Encode() + + connector := runTailnetAPIConnector(ctx, options.Logger, + agentID, coordinateURL.String(), + &websocket.DialOptions{ + HTTPClient: c.client.HTTPClient, + HTTPHeader: headers, + // Need to disable compression to avoid a data-race. + CompressionMode: websocket.CompressionDisabled, + }, + conn, + ) + options.Logger.Debug(ctx, "running tailnet API v2+ connector") + + select { + case <-dialCtx.Done(): + return nil, xerrors.Errorf("timed out waiting for coordinator and derp map: %w", dialCtx.Err()) + case err = <-connector.connected: + if err != nil { + options.Logger.Error(ctx, "failed to connect to tailnet v2+ API", slog.Error(err)) + return nil, xerrors.Errorf("start connector: %w", err) + } + options.Logger.Debug(ctx, "connected to tailnet v2+ API") + } + + agentConn = NewAgentConn(conn, AgentConnOptions{ + AgentID: agentID, + CloseFunc: func() error { + cancel() + <-connector.closed + return conn.Close() + }, + }) + + if !agentConn.AwaitReachable(dialCtx) { + _ = agentConn.Close() + return nil, xerrors.Errorf("timed out waiting for agent to become reachable: %w", dialCtx.Err()) + } + + return agentConn, nil +} + +// @typescript-ignore:WorkspaceAgentReconnectingPTYOpts +type WorkspaceAgentReconnectingPTYOpts struct { + AgentID uuid.UUID + Reconnect uuid.UUID + Width uint16 + Height uint16 + Command string + + // SignedToken is an optional signed token from the + // issue-reconnecting-pty-signed-token endpoint. If set, the session token + // on the client will not be sent. + SignedToken string +} + +// AgentReconnectingPTY spawns a PTY that reconnects using the token provided. +// It communicates using `agent.ReconnectingPTYRequest` marshaled as JSON. +// Responses are PTY output that can be rendered. +func (c *Client) AgentReconnectingPTY(ctx context.Context, opts WorkspaceAgentReconnectingPTYOpts) (net.Conn, error) { + serverURL, err := c.client.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/pty", opts.AgentID)) + if err != nil { + return nil, xerrors.Errorf("parse url: %w", err) + } + q := serverURL.Query() + q.Set("reconnect", opts.Reconnect.String()) + q.Set("width", strconv.Itoa(int(opts.Width))) + q.Set("height", strconv.Itoa(int(opts.Height))) + q.Set("command", opts.Command) + // If we're using a signed token, set the query parameter. + if opts.SignedToken != "" { + q.Set(codersdk.SignedAppTokenQueryParameter, opts.SignedToken) + } + serverURL.RawQuery = q.Encode() + + // If we're not using a signed token, we need to set the session token as a + // cookie. + httpClient := c.client.HTTPClient + if opts.SignedToken == "" { + jar, err := cookiejar.New(nil) + if err != nil { + return nil, xerrors.Errorf("create cookie jar: %w", err) + } + jar.SetCookies(serverURL, []*http.Cookie{{ + Name: codersdk.SessionTokenCookie, + Value: c.client.SessionToken(), + }}) + httpClient = &http.Client{ + Jar: jar, + Transport: c.client.HTTPClient.Transport, + } + } + //nolint:bodyclose + conn, res, err := websocket.Dial(ctx, serverURL.String(), &websocket.DialOptions{ + HTTPClient: httpClient, + }) + if err != nil { + if res == nil { + return nil, err + } + return nil, codersdk.ReadBodyAsError(res) + } + return websocket.NetConn(context.Background(), conn, websocket.MessageBinary), nil +} diff --git a/codersdk/workspaceagents_internal_test.go b/codersdk/workspacesdk/workspacesdk_internal_test.go similarity index 95% rename from codersdk/workspaceagents_internal_test.go rename to codersdk/workspacesdk/workspacesdk_internal_test.go index 0228cee1e2..57e6f751ff 100644 --- a/codersdk/workspaceagents_internal_test.go +++ b/codersdk/workspacesdk/workspacesdk_internal_test.go @@ -1,4 +1,4 @@ -package codersdk +package workspacesdk import ( "context" @@ -17,6 +17,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/tailnet/proto" "github.com/coder/coder/v2/tailnet/tailnettest" @@ -50,7 +51,7 @@ func TestTailnetAPIConnector_Disconnects(t *testing.T) { if !assert.NoError(t, err) { return } - ctx, nc := WebsocketNetConn(r.Context(), sws, websocket.MessageBinary) + ctx, nc := codersdk.WebsocketNetConn(r.Context(), sws, websocket.MessageBinary) err = svc.ServeConnV2(ctx, nc, tailnet.StreamID{ Name: "client", ID: clientID, diff --git a/codersdk/workspaceagents_test.go b/codersdk/workspacesdk/workspacesdk_test.go similarity index 97% rename from codersdk/workspaceagents_test.go rename to codersdk/workspacesdk/workspacesdk_test.go index 31a516bfdd..317db44713 100644 --- a/codersdk/workspaceagents_test.go +++ b/codersdk/workspacesdk/workspacesdk_test.go @@ -1,4 +1,4 @@ -package codersdk_test +package workspacesdk_test import ( "net/url" diff --git a/docs/api/agents.md b/docs/api/agents.md index 75ec45d751..0d73ca9262 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -890,9 +890,9 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/con ### Responses -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceAgentConnectionInfo](schemas.md#codersdkworkspaceagentconnectioninfo) | +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [workspacesdk.AgentConnectionInfo](schemas.md#workspacesdkagentconnectioninfo) | To perform this operation, you must be authenticated. [Learn more](authentication.md). diff --git a/docs/api/debug.md b/docs/api/debug.md index 6f9bee2f55..917088de57 100644 --- a/docs/api/debug.md +++ b/docs/api/debug.md @@ -371,9 +371,9 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \ ### Responses -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------ | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.HealthcheckReport](schemas.md#codersdkhealthcheckreport) | +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [healthsdk.HealthcheckReport](schemas.md#healthsdkhealthcheckreport) | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -402,9 +402,9 @@ curl -X GET http://coder-server:8080/api/v2/debug/health/settings \ ### Responses -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.HealthSettings](schemas.md#codersdkhealthsettings) | +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [healthsdk.HealthSettings](schemas.md#healthsdkhealthsettings) | To perform this operation, you must be authenticated. [Learn more](authentication.md). @@ -432,9 +432,9 @@ curl -X PUT http://coder-server:8080/api/v2/debug/health/settings \ ### Parameters -| Name | In | Type | Required | Description | -| ------ | ---- | ------------------------------------------------------------------------ | -------- | ---------------------- | -| `body` | body | [codersdk.UpdateHealthSettings](schemas.md#codersdkupdatehealthsettings) | true | Update health settings | +| Name | In | Type | Required | Description | +| ------ | ---- | -------------------------------------------------------------------------- | -------- | ---------------------- | +| `body` | body | [healthsdk.UpdateHealthSettings](schemas.md#healthsdkupdatehealthsettings) | true | Update health settings | ### Example responses @@ -448,9 +448,9 @@ curl -X PUT http://coder-server:8080/api/v2/debug/health/settings \ ### Responses -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------------ | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UpdateHealthSettings](schemas.md#codersdkupdatehealthsettings) | +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [healthsdk.UpdateHealthSettings](schemas.md#healthsdkupdatehealthsettings) | To perform this operation, you must be authenticated. [Learn more](authentication.md). diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 710f7d93fb..f46e02a636 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -701,49 +701,6 @@ | `all` | | `application_connect` | -## codersdk.AccessURLReport - -```json -{ - "access_url": "string", - "dismissed": true, - "error": "string", - "healthy": true, - "healthz_response": "string", - "reachable": true, - "severity": "ok", - "status_code": 0, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------ | ----------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | -| `access_url` | string | false | | | -| `dismissed` | boolean | false | | | -| `error` | string | false | | | -| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | -| `healthz_response` | string | false | | | -| `reachable` | boolean | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `status_code` | integer | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------- | --------- | -| `severity` | `ok` | -| `severity` | `warning` | -| `severity` | `error` | - ## codersdk.AddLicenseRequest ```json @@ -1798,308 +1755,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `path` | string | false | | | | `url` | string | false | | | -## codersdk.DERPHealthReport - -```json -{ - "dismissed": true, - "error": "string", - "healthy": true, - "netcheck": { - "captivePortal": "string", - "globalV4": "string", - "globalV6": "string", - "hairPinning": "string", - "icmpv4": true, - "ipv4": true, - "ipv4CanSend": true, - "ipv6": true, - "ipv6CanSend": true, - "mappingVariesByDestIP": "string", - "oshasIPv6": true, - "pcp": "string", - "pmp": "string", - "preferredDERP": 0, - "regionLatency": { - "property1": 0, - "property2": 0 - }, - "regionV4Latency": { - "property1": 0, - "property2": 0 - }, - "regionV6Latency": { - "property1": 0, - "property2": 0 - }, - "udp": true, - "upnP": "string" - }, - "netcheck_err": "string", - "netcheck_logs": ["string"], - "regions": { - "property1": { - "error": "string", - "healthy": true, - "node_reports": [ - { - "can_exchange_messages": true, - "client_errs": [["string"]], - "client_logs": [["string"]], - "error": "string", - "healthy": true, - "node": { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - }, - "node_info": { - "tokenBucketBytesBurst": 0, - "tokenBucketBytesPerSecond": 0 - }, - "round_trip_ping": "string", - "round_trip_ping_ms": 0, - "severity": "ok", - "stun": { - "canSTUN": true, - "enabled": true, - "error": "string" - }, - "uses_websocket": true, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - ], - "region": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - }, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - }, - "property2": { - "error": "string", - "healthy": true, - "node_reports": [ - { - "can_exchange_messages": true, - "client_errs": [["string"]], - "client_logs": [["string"]], - "error": "string", - "healthy": true, - "node": { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - }, - "node_info": { - "tokenBucketBytesBurst": 0, - "tokenBucketBytesPerSecond": 0 - }, - "round_trip_ping": "string", - "round_trip_ping_ms": 0, - "severity": "ok", - "stun": { - "canSTUN": true, - "enabled": true, - "error": "string" - }, - "uses_websocket": true, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - ], - "region": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - }, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - }, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------------------------------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------- | -| `dismissed` | boolean | false | | | -| `error` | string | false | | | -| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | -| `netcheck` | [netcheck.Report](#netcheckreport) | false | | | -| `netcheck_err` | string | false | | | -| `netcheck_logs` | array of string | false | | | -| `regions` | object | false | | | -| » `[any property]` | [codersdk.DERPRegionReport](#codersdkderpregionreport) | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------- | --------- | -| `severity` | `ok` | -| `severity` | `warning` | -| `severity` | `error` | - -## codersdk.DERPNodeReport - -```json -{ - "can_exchange_messages": true, - "client_errs": [["string"]], - "client_logs": [["string"]], - "error": "string", - "healthy": true, - "node": { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - }, - "node_info": { - "tokenBucketBytesBurst": 0, - "tokenBucketBytesPerSecond": 0 - }, - "round_trip_ping": "string", - "round_trip_ping_ms": 0, - "severity": "ok", - "stun": { - "canSTUN": true, - "enabled": true, - "error": "string" - }, - "uses_websocket": true, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ----------------------- | ------------------------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------- | -| `can_exchange_messages` | boolean | false | | | -| `client_errs` | array of array | false | | | -| `client_logs` | array of array | false | | | -| `error` | string | false | | | -| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | -| `node` | [tailcfg.DERPNode](#tailcfgderpnode) | false | | | -| `node_info` | [derp.ServerInfoMessage](#derpserverinfomessage) | false | | | -| `round_trip_ping` | string | false | | | -| `round_trip_ping_ms` | integer | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `stun` | [codersdk.STUNReport](#codersdkstunreport) | false | | | -| `uses_websocket` | boolean | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------- | --------- | -| `severity` | `ok` | -| `severity` | `warning` | -| `severity` | `error` | - ## codersdk.DERPRegion ```json @@ -2116,108 +1771,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `latency_ms` | number | false | | | | `preferred` | boolean | false | | | -## codersdk.DERPRegionReport - -```json -{ - "error": "string", - "healthy": true, - "node_reports": [ - { - "can_exchange_messages": true, - "client_errs": [["string"]], - "client_logs": [["string"]], - "error": "string", - "healthy": true, - "node": { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - }, - "node_info": { - "tokenBucketBytesBurst": 0, - "tokenBucketBytesPerSecond": 0 - }, - "round_trip_ping": "string", - "round_trip_ping_ms": 0, - "severity": "ok", - "stun": { - "canSTUN": true, - "enabled": true, - "error": "string" - }, - "uses_websocket": true, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - ], - "region": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - }, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------- | ----------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | -| `error` | string | false | | | -| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | -| `node_reports` | array of [codersdk.DERPNodeReport](#codersdkderpnodereport) | false | | | -| `region` | [tailcfg.DERPRegion](#tailcfgderpregion) | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------- | --------- | -| `severity` | `ok` | -| `severity` | `warning` | -| `severity` | `error` | - ## codersdk.DERPServerConfig ```json @@ -2272,49 +1825,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `allow_path_app_sharing` | boolean | false | | | | `allow_path_app_site_owner_access` | boolean | false | | | -## codersdk.DatabaseReport - -```json -{ - "dismissed": true, - "error": "string", - "healthy": true, - "latency": "string", - "latency_ms": 0, - "reachable": true, - "severity": "ok", - "threshold_ms": 0, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------- | ----------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | -| `dismissed` | boolean | false | | | -| `error` | string | false | | | -| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | -| `latency` | string | false | | | -| `latency_ms` | integer | false | | | -| `reachable` | boolean | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `threshold_ms` | integer | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------- | --------- | -| `severity` | `ok` | -| `severity` | `warning` | -| `severity` | `error` | - ## codersdk.DeleteWorkspaceAgentPortShareRequest ```json @@ -3472,39 +2982,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `user` | | `oidc` | -## codersdk.HealthSection - -```json -"DERP" -``` - -### Properties - -#### Enumerated Values - -| Value | -| -------------------- | -| `DERP` | -| `AccessURL` | -| `Websocket` | -| `Database` | -| `WorkspaceProxy` | -| `ProvisionerDaemons` | - -## codersdk.HealthSettings - -```json -{ - "dismissed_healthchecks": ["DERP"] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------------ | --------------------------------------------------------- | -------- | ------------ | ----------- | -| `dismissed_healthchecks` | array of [codersdk.HealthSection](#codersdkhealthsection) | false | | | - ## codersdk.Healthcheck ```json @@ -3539,358 +3016,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `refresh` | integer | false | | | | `threshold_database` | integer | false | | | -## codersdk.HealthcheckReport - -```json -{ - "access_url": { - "access_url": "string", - "dismissed": true, - "error": "string", - "healthy": true, - "healthz_response": "string", - "reachable": true, - "severity": "ok", - "status_code": 0, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - }, - "coder_version": "string", - "database": { - "dismissed": true, - "error": "string", - "healthy": true, - "latency": "string", - "latency_ms": 0, - "reachable": true, - "severity": "ok", - "threshold_ms": 0, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - }, - "derp": { - "dismissed": true, - "error": "string", - "healthy": true, - "netcheck": { - "captivePortal": "string", - "globalV4": "string", - "globalV6": "string", - "hairPinning": "string", - "icmpv4": true, - "ipv4": true, - "ipv4CanSend": true, - "ipv6": true, - "ipv6CanSend": true, - "mappingVariesByDestIP": "string", - "oshasIPv6": true, - "pcp": "string", - "pmp": "string", - "preferredDERP": 0, - "regionLatency": { - "property1": 0, - "property2": 0 - }, - "regionV4Latency": { - "property1": 0, - "property2": 0 - }, - "regionV6Latency": { - "property1": 0, - "property2": 0 - }, - "udp": true, - "upnP": "string" - }, - "netcheck_err": "string", - "netcheck_logs": ["string"], - "regions": { - "property1": { - "error": "string", - "healthy": true, - "node_reports": [ - { - "can_exchange_messages": true, - "client_errs": [["string"]], - "client_logs": [["string"]], - "error": "string", - "healthy": true, - "node": { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - }, - "node_info": { - "tokenBucketBytesBurst": 0, - "tokenBucketBytesPerSecond": 0 - }, - "round_trip_ping": "string", - "round_trip_ping_ms": 0, - "severity": "ok", - "stun": { - "canSTUN": true, - "enabled": true, - "error": "string" - }, - "uses_websocket": true, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - ], - "region": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - }, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - }, - "property2": { - "error": "string", - "healthy": true, - "node_reports": [ - { - "can_exchange_messages": true, - "client_errs": [["string"]], - "client_logs": [["string"]], - "error": "string", - "healthy": true, - "node": { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - }, - "node_info": { - "tokenBucketBytesBurst": 0, - "tokenBucketBytesPerSecond": 0 - }, - "round_trip_ping": "string", - "round_trip_ping_ms": 0, - "severity": "ok", - "stun": { - "canSTUN": true, - "enabled": true, - "error": "string" - }, - "uses_websocket": true, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - ], - "region": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - }, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - }, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - }, - "failing_sections": ["DERP"], - "healthy": true, - "provisioner_daemons": { - "dismissed": true, - "error": "string", - "items": [ - { - "provisioner_daemon": { - "api_version": "string", - "created_at": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "last_seen_at": "2019-08-24T14:15:22Z", - "name": "string", - "provisioners": ["string"], - "tags": { - "property1": "string", - "property2": "string" - }, - "version": "string" - }, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - ], - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - }, - "severity": "ok", - "time": "2019-08-24T14:15:22Z", - "websocket": { - "body": "string", - "code": 0, - "dismissed": true, - "error": "string", - "healthy": true, - "severity": "ok", - "warnings": ["string"] - }, - "workspace_proxy": { - "dismissed": true, - "error": "string", - "healthy": true, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ], - "workspace_proxies": { - "regions": [ - { - "created_at": "2019-08-24T14:15:22Z", - "deleted": true, - "derp_enabled": true, - "derp_only": true, - "display_name": "string", - "healthy": true, - "icon_url": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "name": "string", - "path_app_url": "string", - "status": { - "checked_at": "2019-08-24T14:15:22Z", - "report": { - "errors": ["string"], - "warnings": ["string"] - }, - "status": "ok" - }, - "updated_at": "2019-08-24T14:15:22Z", - "version": "string", - "wildcard_hostname": "string" - } - ] - } - } -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| --------------------- | ---------------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------- | -| `access_url` | [codersdk.AccessURLReport](#codersdkaccessurlreport) | false | | | -| `coder_version` | string | false | | The Coder version of the server that the report was generated on. | -| `database` | [codersdk.DatabaseReport](#codersdkdatabasereport) | false | | | -| `derp` | [codersdk.DERPHealthReport](#codersdkderphealthreport) | false | | | -| `failing_sections` | array of [codersdk.HealthSection](#codersdkhealthsection) | false | | Failing sections is a list of sections that have failed their healthcheck. | -| `healthy` | boolean | false | | Healthy is true if the report returns no errors. Deprecated: use `Severity` instead | -| `provisioner_daemons` | [codersdk.ProvisionerDaemonsReport](#codersdkprovisionerdaemonsreport) | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | Severity indicates the status of Coder health. | -| `time` | string | false | | Time is the time the report was generated at. | -| `websocket` | [codersdk.WebsocketReport](#codersdkwebsocketreport) | false | | | -| `workspace_proxy` | [codersdk.WorkspaceProxyReport](#codersdkworkspaceproxyreport) | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------- | --------- | -| `severity` | `ok` | -| `severity` | `warning` | -| `severity` | `error` | - ## codersdk.InsightsReportInterval ```json @@ -4605,88 +3730,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | » `[any property]` | string | false | | | | `version` | string | false | | | -## codersdk.ProvisionerDaemonsReport - -```json -{ - "dismissed": true, - "error": "string", - "items": [ - { - "provisioner_daemon": { - "api_version": "string", - "created_at": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "last_seen_at": "2019-08-24T14:15:22Z", - "name": "string", - "provisioners": ["string"], - "tags": { - "property1": "string", - "property2": "string" - }, - "version": "string" - }, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] - } - ], - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ----------- | --------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `dismissed` | boolean | false | | | -| `error` | string | false | | | -| `items` | array of [codersdk.ProvisionerDaemonsReportItem](#codersdkprovisionerdaemonsreportitem) | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | - -## codersdk.ProvisionerDaemonsReportItem - -```json -{ - "provisioner_daemon": { - "api_version": "string", - "created_at": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "last_seen_at": "2019-08-24T14:15:22Z", - "name": "string", - "provisioners": ["string"], - "tags": { - "property1": "string", - "property2": "string" - }, - "version": "string" - }, - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------------- | -------------------------------------------------------- | -------- | ------------ | ----------- | -| `provisioner_daemon` | [codersdk.ProvisionerDaemon](#codersdkprovisionerdaemon) | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | - ## codersdk.ProvisionerJob ```json @@ -5211,24 +4254,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `ssh_config_options` | object | false | | | | » `[any property]` | string | false | | | -## codersdk.STUNReport - -```json -{ - "canSTUN": true, - "enabled": true, - "error": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| --------- | ------- | -------- | ------------ | ----------- | -| `canSTUN` | boolean | false | | | -| `enabled` | boolean | false | | | -| `error` | string | false | | | - ## codersdk.ServiceBannerConfig ```json @@ -6155,20 +5180,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `url` | string | false | | URL to download the latest release of Coder. | | `version` | string | false | | Version is the semantic version for the latest release of Coder. | -## codersdk.UpdateHealthSettings - -```json -{ - "dismissed_healthchecks": ["DERP"] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------------ | --------------------------------------------------------- | -------- | ------------ | ----------- | -| `dismissed_healthchecks` | array of [codersdk.HealthSection](#codersdkhealthsection) | false | | | - ## codersdk.UpdateRoles ```json @@ -6728,40 +5739,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `name` | string | false | | | | `value` | string | false | | | -## codersdk.WebsocketReport - -```json -{ - "body": "string", - "code": 0, - "dismissed": true, - "error": "string", - "healthy": true, - "severity": "ok", - "warnings": ["string"] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ----------- | ---------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | -| `body` | string | false | | | -| `code` | integer | false | | | -| `dismissed` | boolean | false | | | -| `error` | string | false | | | -| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `warnings` | array of string | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------- | --------- | -| `severity` | `ok` | -| `severity` | `warning` | -| `severity` | `error` | - ## codersdk.Workspace ```json @@ -7114,82 +6091,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `updated_at` | string | false | | | | `version` | string | false | | | -## codersdk.WorkspaceAgentConnectionInfo - -```json -{ - "derp_force_websockets": true, - "derp_map": { - "homeParams": { - "regionScore": { - "property1": 0, - "property2": 0 - } - }, - "omitDefaultRegions": true, - "regions": { - "property1": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - }, - "property2": { - "avoid": true, - "embeddedRelay": true, - "nodes": [ - { - "canPort80": true, - "certName": "string", - "derpport": 0, - "forceHTTP": true, - "hostName": "string", - "insecureForTests": true, - "ipv4": "string", - "ipv6": "string", - "name": "string", - "regionID": 0, - "stunonly": true, - "stunport": 0, - "stuntestIP": "string" - } - ], - "regionCode": "string", - "regionID": 0, - "regionName": "string" - } - } - }, - "disable_direct_connections": true -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ---------------------------- | ---------------------------------- | -------- | ------------ | ----------- | -| `derp_force_websockets` | boolean | false | | | -| `derp_map` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | | -| `disable_direct_connections` | boolean | false | | | - ## codersdk.WorkspaceAgentHealth ```json @@ -7884,61 +6785,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `version` | string | false | | | | `wildcard_hostname` | string | false | | Wildcard hostname is the wildcard hostname for subdomain apps. E.g. _.us.example.com E.g. _--suffix.au.example.com Optional. Does not need to be on the same domain as PathAppURL. | -## codersdk.WorkspaceProxyReport - -```json -{ - "dismissed": true, - "error": "string", - "healthy": true, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ], - "workspace_proxies": { - "regions": [ - { - "created_at": "2019-08-24T14:15:22Z", - "deleted": true, - "derp_enabled": true, - "derp_only": true, - "display_name": "string", - "healthy": true, - "icon_url": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "name": "string", - "path_app_url": "string", - "status": { - "checked_at": "2019-08-24T14:15:22Z", - "report": { - "errors": ["string"], - "warnings": ["string"] - }, - "status": "ok" - }, - "updated_at": "2019-08-24T14:15:22Z", - "version": "string", - "wildcard_hostname": "string" - } - ] - } -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------- | ---------------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `dismissed` | boolean | false | | | -| `error` | string | false | | | -| `healthy` | boolean | false | | | -| `severity` | [health.Severity](#healthseverity) | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | -| `workspace_proxies` | [codersdk.RegionsResponse-codersdk_WorkspaceProxy](#codersdkregionsresponse-codersdk_workspaceproxy) | false | | | - ## codersdk.WorkspaceProxyStatus ```json @@ -8459,6 +7305,1084 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `warning` | | `error` | +## healthsdk.AccessURLReport + +```json +{ + "access_url": "string", + "dismissed": true, + "error": "string", + "healthy": true, + "healthz_response": "string", + "reachable": true, + "severity": "ok", + "status_code": 0, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------ | ----------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | +| `access_url` | string | false | | | +| `dismissed` | boolean | false | | | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | +| `healthz_response` | string | false | | | +| `reachable` | boolean | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `status_code` | integer | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | + +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## healthsdk.DERPHealthReport + +```json +{ + "dismissed": true, + "error": "string", + "healthy": true, + "netcheck": { + "captivePortal": "string", + "globalV4": "string", + "globalV6": "string", + "hairPinning": "string", + "icmpv4": true, + "ipv4": true, + "ipv4CanSend": true, + "ipv6": true, + "ipv6CanSend": true, + "mappingVariesByDestIP": "string", + "oshasIPv6": true, + "pcp": "string", + "pmp": "string", + "preferredDERP": 0, + "regionLatency": { + "property1": 0, + "property2": 0 + }, + "regionV4Latency": { + "property1": 0, + "property2": 0 + }, + "regionV6Latency": { + "property1": 0, + "property2": 0 + }, + "udp": true, + "upnP": "string" + }, + "netcheck_err": "string", + "netcheck_logs": ["string"], + "regions": { + "property1": { + "error": "string", + "healthy": true, + "node_reports": [ + { + "can_exchange_messages": true, + "client_errs": [["string"]], + "client_logs": [["string"]], + "error": "string", + "healthy": true, + "node": { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + }, + "node_info": { + "tokenBucketBytesBurst": 0, + "tokenBucketBytesPerSecond": 0 + }, + "round_trip_ping": "string", + "round_trip_ping_ms": 0, + "severity": "ok", + "stun": { + "canSTUN": true, + "enabled": true, + "error": "string" + }, + "uses_websocket": true, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + ], + "region": { + "avoid": true, + "embeddedRelay": true, + "nodes": [ + { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + } + ], + "regionCode": "string", + "regionID": 0, + "regionName": "string" + }, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + }, + "property2": { + "error": "string", + "healthy": true, + "node_reports": [ + { + "can_exchange_messages": true, + "client_errs": [["string"]], + "client_logs": [["string"]], + "error": "string", + "healthy": true, + "node": { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + }, + "node_info": { + "tokenBucketBytesBurst": 0, + "tokenBucketBytesPerSecond": 0 + }, + "round_trip_ping": "string", + "round_trip_ping_ms": 0, + "severity": "ok", + "stun": { + "canSTUN": true, + "enabled": true, + "error": "string" + }, + "uses_websocket": true, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + ], + "region": { + "avoid": true, + "embeddedRelay": true, + "nodes": [ + { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + } + ], + "regionCode": "string", + "regionID": 0, + "regionName": "string" + }, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + }, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------ | -------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | +| `dismissed` | boolean | false | | | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | +| `netcheck` | [netcheck.Report](#netcheckreport) | false | | | +| `netcheck_err` | string | false | | | +| `netcheck_logs` | array of string | false | | | +| `regions` | object | false | | | +| » `[any property]` | [healthsdk.DERPRegionReport](#healthsdkderpregionreport) | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | + +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## healthsdk.DERPNodeReport + +```json +{ + "can_exchange_messages": true, + "client_errs": [["string"]], + "client_logs": [["string"]], + "error": "string", + "healthy": true, + "node": { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + }, + "node_info": { + "tokenBucketBytesBurst": 0, + "tokenBucketBytesPerSecond": 0 + }, + "round_trip_ping": "string", + "round_trip_ping_ms": 0, + "severity": "ok", + "stun": { + "canSTUN": true, + "enabled": true, + "error": "string" + }, + "uses_websocket": true, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ----------------------- | ------------------------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------- | +| `can_exchange_messages` | boolean | false | | | +| `client_errs` | array of array | false | | | +| `client_logs` | array of array | false | | | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | +| `node` | [tailcfg.DERPNode](#tailcfgderpnode) | false | | | +| `node_info` | [derp.ServerInfoMessage](#derpserverinfomessage) | false | | | +| `round_trip_ping` | string | false | | | +| `round_trip_ping_ms` | integer | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `stun` | [healthsdk.STUNReport](#healthsdkstunreport) | false | | | +| `uses_websocket` | boolean | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | + +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## healthsdk.DERPRegionReport + +```json +{ + "error": "string", + "healthy": true, + "node_reports": [ + { + "can_exchange_messages": true, + "client_errs": [["string"]], + "client_logs": [["string"]], + "error": "string", + "healthy": true, + "node": { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + }, + "node_info": { + "tokenBucketBytesBurst": 0, + "tokenBucketBytesPerSecond": 0 + }, + "round_trip_ping": "string", + "round_trip_ping_ms": 0, + "severity": "ok", + "stun": { + "canSTUN": true, + "enabled": true, + "error": "string" + }, + "uses_websocket": true, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + ], + "region": { + "avoid": true, + "embeddedRelay": true, + "nodes": [ + { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + } + ], + "regionCode": "string", + "regionID": 0, + "regionName": "string" + }, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------- | ------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | +| `node_reports` | array of [healthsdk.DERPNodeReport](#healthsdkderpnodereport) | false | | | +| `region` | [tailcfg.DERPRegion](#tailcfgderpregion) | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | + +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## healthsdk.DatabaseReport + +```json +{ + "dismissed": true, + "error": "string", + "healthy": true, + "latency": "string", + "latency_ms": 0, + "reachable": true, + "severity": "ok", + "threshold_ms": 0, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------- | ----------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | +| `dismissed` | boolean | false | | | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | +| `latency` | string | false | | | +| `latency_ms` | integer | false | | | +| `reachable` | boolean | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `threshold_ms` | integer | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | + +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## healthsdk.HealthSection + +```json +"DERP" +``` + +### Properties + +#### Enumerated Values + +| Value | +| -------------------- | +| `DERP` | +| `AccessURL` | +| `Websocket` | +| `Database` | +| `WorkspaceProxy` | +| `ProvisionerDaemons` | + +## healthsdk.HealthSettings + +```json +{ + "dismissed_healthchecks": ["DERP"] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------------ | ----------------------------------------------------------- | -------- | ------------ | ----------- | +| `dismissed_healthchecks` | array of [healthsdk.HealthSection](#healthsdkhealthsection) | false | | | + +## healthsdk.HealthcheckReport + +```json +{ + "access_url": { + "access_url": "string", + "dismissed": true, + "error": "string", + "healthy": true, + "healthz_response": "string", + "reachable": true, + "severity": "ok", + "status_code": 0, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + }, + "coder_version": "string", + "database": { + "dismissed": true, + "error": "string", + "healthy": true, + "latency": "string", + "latency_ms": 0, + "reachable": true, + "severity": "ok", + "threshold_ms": 0, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + }, + "derp": { + "dismissed": true, + "error": "string", + "healthy": true, + "netcheck": { + "captivePortal": "string", + "globalV4": "string", + "globalV6": "string", + "hairPinning": "string", + "icmpv4": true, + "ipv4": true, + "ipv4CanSend": true, + "ipv6": true, + "ipv6CanSend": true, + "mappingVariesByDestIP": "string", + "oshasIPv6": true, + "pcp": "string", + "pmp": "string", + "preferredDERP": 0, + "regionLatency": { + "property1": 0, + "property2": 0 + }, + "regionV4Latency": { + "property1": 0, + "property2": 0 + }, + "regionV6Latency": { + "property1": 0, + "property2": 0 + }, + "udp": true, + "upnP": "string" + }, + "netcheck_err": "string", + "netcheck_logs": ["string"], + "regions": { + "property1": { + "error": "string", + "healthy": true, + "node_reports": [ + { + "can_exchange_messages": true, + "client_errs": [["string"]], + "client_logs": [["string"]], + "error": "string", + "healthy": true, + "node": { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + }, + "node_info": { + "tokenBucketBytesBurst": 0, + "tokenBucketBytesPerSecond": 0 + }, + "round_trip_ping": "string", + "round_trip_ping_ms": 0, + "severity": "ok", + "stun": { + "canSTUN": true, + "enabled": true, + "error": "string" + }, + "uses_websocket": true, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + ], + "region": { + "avoid": true, + "embeddedRelay": true, + "nodes": [ + { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + } + ], + "regionCode": "string", + "regionID": 0, + "regionName": "string" + }, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + }, + "property2": { + "error": "string", + "healthy": true, + "node_reports": [ + { + "can_exchange_messages": true, + "client_errs": [["string"]], + "client_logs": [["string"]], + "error": "string", + "healthy": true, + "node": { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + }, + "node_info": { + "tokenBucketBytesBurst": 0, + "tokenBucketBytesPerSecond": 0 + }, + "round_trip_ping": "string", + "round_trip_ping_ms": 0, + "severity": "ok", + "stun": { + "canSTUN": true, + "enabled": true, + "error": "string" + }, + "uses_websocket": true, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + ], + "region": { + "avoid": true, + "embeddedRelay": true, + "nodes": [ + { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + } + ], + "regionCode": "string", + "regionID": 0, + "regionName": "string" + }, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + }, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + }, + "failing_sections": ["DERP"], + "healthy": true, + "provisioner_daemons": { + "dismissed": true, + "error": "string", + "items": [ + { + "provisioner_daemon": { + "api_version": "string", + "created_at": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "last_seen_at": "2019-08-24T14:15:22Z", + "name": "string", + "provisioners": ["string"], + "tags": { + "property1": "string", + "property2": "string" + }, + "version": "string" + }, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + ], + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + }, + "severity": "ok", + "time": "2019-08-24T14:15:22Z", + "websocket": { + "body": "string", + "code": 0, + "dismissed": true, + "error": "string", + "healthy": true, + "severity": "ok", + "warnings": ["string"] + }, + "workspace_proxy": { + "dismissed": true, + "error": "string", + "healthy": true, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ], + "workspace_proxies": { + "regions": [ + { + "created_at": "2019-08-24T14:15:22Z", + "deleted": true, + "derp_enabled": true, + "derp_only": true, + "display_name": "string", + "healthy": true, + "icon_url": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "path_app_url": "string", + "status": { + "checked_at": "2019-08-24T14:15:22Z", + "report": { + "errors": ["string"], + "warnings": ["string"] + }, + "status": "ok" + }, + "updated_at": "2019-08-24T14:15:22Z", + "version": "string", + "wildcard_hostname": "string" + } + ] + } + } +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| --------------------- | ------------------------------------------------------------------------ | -------- | ------------ | ----------------------------------------------------------------------------------- | +| `access_url` | [healthsdk.AccessURLReport](#healthsdkaccessurlreport) | false | | | +| `coder_version` | string | false | | The Coder version of the server that the report was generated on. | +| `database` | [healthsdk.DatabaseReport](#healthsdkdatabasereport) | false | | | +| `derp` | [healthsdk.DERPHealthReport](#healthsdkderphealthreport) | false | | | +| `failing_sections` | array of [healthsdk.HealthSection](#healthsdkhealthsection) | false | | Failing sections is a list of sections that have failed their healthcheck. | +| `healthy` | boolean | false | | Healthy is true if the report returns no errors. Deprecated: use `Severity` instead | +| `provisioner_daemons` | [healthsdk.ProvisionerDaemonsReport](#healthsdkprovisionerdaemonsreport) | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | Severity indicates the status of Coder health. | +| `time` | string | false | | Time is the time the report was generated at. | +| `websocket` | [healthsdk.WebsocketReport](#healthsdkwebsocketreport) | false | | | +| `workspace_proxy` | [healthsdk.WorkspaceProxyReport](#healthsdkworkspaceproxyreport) | false | | | + +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## healthsdk.ProvisionerDaemonsReport + +```json +{ + "dismissed": true, + "error": "string", + "items": [ + { + "provisioner_daemon": { + "api_version": "string", + "created_at": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "last_seen_at": "2019-08-24T14:15:22Z", + "name": "string", + "provisioners": ["string"], + "tags": { + "property1": "string", + "property2": "string" + }, + "version": "string" + }, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + ], + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ----------- | ----------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `dismissed` | boolean | false | | | +| `error` | string | false | | | +| `items` | array of [healthsdk.ProvisionerDaemonsReportItem](#healthsdkprovisionerdaemonsreportitem) | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | + +## healthsdk.ProvisionerDaemonsReportItem + +```json +{ + "provisioner_daemon": { + "api_version": "string", + "created_at": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "last_seen_at": "2019-08-24T14:15:22Z", + "name": "string", + "provisioners": ["string"], + "tags": { + "property1": "string", + "property2": "string" + }, + "version": "string" + }, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------------- | -------------------------------------------------------- | -------- | ------------ | ----------- | +| `provisioner_daemon` | [codersdk.ProvisionerDaemon](#codersdkprovisionerdaemon) | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | + +## healthsdk.STUNReport + +```json +{ + "canSTUN": true, + "enabled": true, + "error": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| --------- | ------- | -------- | ------------ | ----------- | +| `canSTUN` | boolean | false | | | +| `enabled` | boolean | false | | | +| `error` | string | false | | | + +## healthsdk.UpdateHealthSettings + +```json +{ + "dismissed_healthchecks": ["DERP"] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------------ | ----------------------------------------------------------- | -------- | ------------ | ----------- | +| `dismissed_healthchecks` | array of [healthsdk.HealthSection](#healthsdkhealthsection) | false | | | + +## healthsdk.WebsocketReport + +```json +{ + "body": "string", + "code": 0, + "dismissed": true, + "error": "string", + "healthy": true, + "severity": "ok", + "warnings": ["string"] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ----------- | ---------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | +| `body` | string | false | | | +| `code` | integer | false | | | +| `dismissed` | boolean | false | | | +| `error` | string | false | | | +| `healthy` | boolean | false | | Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead. | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `warnings` | array of string | false | | | + +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## healthsdk.WorkspaceProxyReport + +```json +{ + "dismissed": true, + "error": "string", + "healthy": true, + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ], + "workspace_proxies": { + "regions": [ + { + "created_at": "2019-08-24T14:15:22Z", + "deleted": true, + "derp_enabled": true, + "derp_only": true, + "display_name": "string", + "healthy": true, + "icon_url": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "path_app_url": "string", + "status": { + "checked_at": "2019-08-24T14:15:22Z", + "report": { + "errors": ["string"], + "warnings": ["string"] + }, + "status": "ok" + }, + "updated_at": "2019-08-24T14:15:22Z", + "version": "string", + "wildcard_hostname": "string" + } + ] + } +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------- | ---------------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `dismissed` | boolean | false | | | +| `error` | string | false | | | +| `healthy` | boolean | false | | | +| `severity` | [health.Severity](#healthseverity) | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | +| `workspace_proxies` | [codersdk.RegionsResponse-codersdk_WorkspaceProxy](#codersdkregionsresponse-codersdk_workspaceproxy) | false | | | + ## key.NodePublic ```json @@ -9105,6 +9029,82 @@ _None_ | `user_id` | string | false | | | | `workspace_id` | string | false | | | +## workspacesdk.AgentConnectionInfo + +```json +{ + "derp_force_websockets": true, + "derp_map": { + "homeParams": { + "regionScore": { + "property1": 0, + "property2": 0 + } + }, + "omitDefaultRegions": true, + "regions": { + "property1": { + "avoid": true, + "embeddedRelay": true, + "nodes": [ + { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + } + ], + "regionCode": "string", + "regionID": 0, + "regionName": "string" + }, + "property2": { + "avoid": true, + "embeddedRelay": true, + "nodes": [ + { + "canPort80": true, + "certName": "string", + "derpport": 0, + "forceHTTP": true, + "hostName": "string", + "insecureForTests": true, + "ipv4": "string", + "ipv6": "string", + "name": "string", + "regionID": 0, + "stunonly": true, + "stunport": 0, + "stuntestIP": "string" + } + ], + "regionCode": "string", + "regionID": 0, + "regionName": "string" + } + } + }, + "disable_direct_connections": true +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------------------------- | ---------------------------------- | -------- | ------------ | ----------- | +| `derp_force_websockets` | boolean | false | | | +| `derp_map` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | | +| `disable_direct_connections` | boolean | false | | | + ## wsproxysdk.DeregisterWorkspaceProxyRequest ```json diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index e2074dd43b..1b156a4355 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -25,6 +25,7 @@ import ( "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/enterprise/audit" "github.com/coder/coder/v2/enterprise/coderd" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" @@ -272,7 +273,7 @@ func TestAuditLogging(t *testing.T) { DontAddLicense: true, }) r := setupWorkspaceAgent(t, client, user, 0) - conn, err := client.DialWorkspaceAgent(ctx, r.sdkAgent.ID, nil) //nolint:gocritic // RBAC is not the purpose of this test + conn, err := workspacesdk.New(client).DialAgent(ctx, r.sdkAgent.ID, nil) //nolint:gocritic // RBAC is not the purpose of this test require.NoError(t, err) defer conn.Close() connected := conn.AwaitReachable(ctx) diff --git a/enterprise/coderd/replicas_test.go b/enterprise/coderd/replicas_test.go index 6d348db782..595f2fe375 100644 --- a/enterprise/coderd/replicas_test.go +++ b/enterprise/coderd/replicas_test.go @@ -13,6 +13,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" "github.com/coder/coder/v2/testutil" @@ -82,10 +83,11 @@ func TestReplicas(t *testing.T) { require.Len(t, replicas, 2) r := setupWorkspaceAgent(t, firstClient, firstUser, 0) - conn, err := secondClient.DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, &codersdk.DialWorkspaceAgentOptions{ - BlockEndpoints: true, - Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), - }) + conn, err := workspacesdk.New(secondClient). + DialAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialAgentOptions{ + BlockEndpoints: true, + Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), + }) require.NoError(t, err) require.Eventually(t, func() bool { ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitShort) @@ -128,10 +130,11 @@ func TestReplicas(t *testing.T) { require.Len(t, replicas, 2) r := setupWorkspaceAgent(t, firstClient, firstUser, 0) - conn, err := secondClient.DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, &codersdk.DialWorkspaceAgentOptions{ - BlockEndpoints: true, - Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), - }) + conn, err := workspacesdk.New(secondClient). + DialAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialAgentOptions{ + BlockEndpoints: true, + Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), + }) require.NoError(t, err) require.Eventually(t, func() bool { ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.IntervalSlow) diff --git a/enterprise/coderd/workspaceagents_test.go b/enterprise/coderd/workspaceagents_test.go index a6cf84a594..17b38a0332 100644 --- a/enterprise/coderd/workspaceagents_test.go +++ b/enterprise/coderd/workspaceagents_test.go @@ -15,6 +15,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" "github.com/coder/coder/v2/provisioner/echo" @@ -46,7 +47,7 @@ func TestBlockNonBrowser(t *testing.T) { }) r := setupWorkspaceAgent(t, client, user, 0) //nolint:gocritic // Testing that even the owner gets blocked. - _, err := client.DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, nil) + _, err := workspacesdk.New(client).DialAgent(context.Background(), r.sdkAgent.ID, nil) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusConflict, apiErr.StatusCode()) @@ -65,7 +66,7 @@ func TestBlockNonBrowser(t *testing.T) { }) r := setupWorkspaceAgent(t, client, user, 0) //nolint:gocritic // Testing RBAC is not the point of this test. - conn, err := client.DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, nil) + conn, err := workspacesdk.New(client).DialAgent(context.Background(), r.sdkAgent.ID, nil) require.NoError(t, err) _ = conn.Close() }) diff --git a/enterprise/tailnet/pgcoord_test.go b/enterprise/tailnet/pgcoord_test.go index c11cc9b630..623772b63c 100644 --- a/enterprise/tailnet/pgcoord_test.go +++ b/enterprise/tailnet/pgcoord_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" agpltest "github.com/coder/coder/v2/tailnet/test" "github.com/google/uuid" @@ -228,7 +228,7 @@ func TestPGCoordinatorSingle_AgentValidIPLegacy(t *testing.T) { defer agent.close() agent.sendNode(&agpl.Node{ Addresses: []netip.Prefix{ - netip.PrefixFrom(codersdk.WorkspaceAgentIP, 128), + netip.PrefixFrom(workspacesdk.AgentIP, 128), }, PreferredDERP: 10, }) diff --git a/enterprise/wsproxy/wsproxy_test.go b/enterprise/wsproxy/wsproxy_test.go index c18cd0413f..145a69a95e 100644 --- a/enterprise/wsproxy/wsproxy_test.go +++ b/enterprise/wsproxy/wsproxy_test.go @@ -29,6 +29,7 @@ import ( "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/workspaceapps/apptest" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" "github.com/coder/coder/v2/enterprise/coderd/license" @@ -196,7 +197,7 @@ resourceLoop: t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) - connInfo, err := client.WorkspaceAgentConnectionInfo(ctx, agentID) + connInfo, err := workspacesdk.New(client).AgentConnectionInfo(ctx, agentID) require.NoError(t, err) // There should be three DERP regions in the map: the primary, and each @@ -269,7 +270,7 @@ resourceLoop: t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) - connInfo, err := client.WorkspaceAgentConnectionInfo(ctx, agentID) + connInfo, err := workspacesdk.New(client).AgentConnectionInfo(ctx, agentID) require.NoError(t, err) require.NotNil(t, connInfo.DERPMap) require.Len(t, connInfo.DERPMap.Regions, 3+len(api.DeploymentValues.DERP.Server.STUNAddresses.Value())) @@ -429,13 +430,14 @@ resourceLoop: _ = coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Connect to the workspace agent. - conn, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{ - Logger: slogtest.Make(t, &slogtest.Options{ - IgnoreErrors: true, - }).Named("client").Leveled(slog.LevelDebug), - // Force DERP. - BlockEndpoints: true, - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ + Logger: slogtest.Make(t, &slogtest.Options{ + IgnoreErrors: true, + }).Named("client").Leveled(slog.LevelDebug), + // Force DERP. + BlockEndpoints: true, + }) require.NoError(t, err) t.Cleanup(func() { err := conn.Close() diff --git a/enterprise/wsproxy/wsproxysdk/wsproxysdk.go b/enterprise/wsproxy/wsproxysdk/wsproxysdk.go index 37636102bb..b3b833fb37 100644 --- a/enterprise/wsproxy/wsproxysdk/wsproxysdk.go +++ b/enterprise/wsproxy/wsproxysdk/wsproxysdk.go @@ -18,6 +18,7 @@ import ( "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" agpl "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/tailnet/proto" ) @@ -76,8 +77,8 @@ func (c *Client) RequestIgnoreRedirects(ctx context.Context, method, path string // DialWorkspaceAgent calls the underlying codersdk.Client's DialWorkspaceAgent // method. -func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, options *codersdk.DialWorkspaceAgentOptions) (agentConn *codersdk.WorkspaceAgentConn, err error) { - return c.SDKClient.DialWorkspaceAgent(ctx, agentID, options) +func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, options *workspacesdk.DialAgentOptions) (agentConn *workspacesdk.AgentConn, err error) { + return workspacesdk.New(c.SDKClient).DialAgent(ctx, agentID, options) } type IssueSignedAppTokenResponse struct { diff --git a/scaletest/agentconn/run.go b/scaletest/agentconn/run.go index cc942448ff..a5aaddee4e 100644 --- a/scaletest/agentconn/run.go +++ b/scaletest/agentconn/run.go @@ -17,6 +17,7 @@ import ( "cdr.dev/slog/sloggers/sloghuman" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/scaletest/harness" "github.com/coder/coder/v2/scaletest/loadtestutil" ) @@ -62,11 +63,12 @@ func (r *Runner) Run(ctx context.Context, _ string, w io.Writer) error { _, _ = fmt.Fprintln(logs, "\tUsing proxied DERP connection through coder server...") } - conn, err := r.client.DialWorkspaceAgent(ctx, r.cfg.AgentID, &codersdk.DialWorkspaceAgentOptions{ - Logger: logger.Named("agentconn"), - // If the config requested DERP, then force DERP. - BlockEndpoints: r.cfg.ConnectionMode == ConnectionModeDerp, - }) + conn, err := workspacesdk.New(r.client). + DialAgent(ctx, r.cfg.AgentID, &workspacesdk.DialAgentOptions{ + Logger: logger.Named("agentconn"), + // If the config requested DERP, then force DERP. + BlockEndpoints: r.cfg.ConnectionMode == ConnectionModeDerp, + }) if err != nil { return xerrors.Errorf("dial workspace agent: %w", err) } @@ -131,7 +133,7 @@ func (r *Runner) Run(ctx context.Context, _ string, w io.Writer) error { return nil } -func waitForDisco(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn) error { +func waitForDisco(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error { const pingAttempts = 10 const pingDelay = 1 * time.Second @@ -163,7 +165,7 @@ func waitForDisco(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceA return nil } -func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn) error { +func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error { const directConnectionAttempts = 30 const directConnectionDelay = 1 * time.Second @@ -205,7 +207,7 @@ func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *codersdk return nil } -func verifyConnection(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn) error { +func verifyConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error { const verifyConnectionAttempts = 30 const verifyConnectionDelay = 1 * time.Second @@ -219,7 +221,7 @@ func verifyConnection(ctx context.Context, logs io.Writer, conn *codersdk.Worksp u := &url.URL{ Scheme: "http", - Host: net.JoinHostPort("localhost", strconv.Itoa(codersdk.WorkspaceAgentHTTPAPIServerPort)), + Host: net.JoinHostPort("localhost", strconv.Itoa(workspacesdk.AgentHTTPAPIServerPort)), Path: "/", } req, err := http.NewRequestWithContext(verifyCtx, http.MethodGet, u.String(), nil) @@ -247,7 +249,7 @@ func verifyConnection(ctx context.Context, logs io.Writer, conn *codersdk.Worksp return nil } -func performInitialConnections(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn, specs []Connection) error { +func performInitialConnections(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn, specs []Connection) error { if len(specs) == 0 { return nil } @@ -285,7 +287,7 @@ func performInitialConnections(ctx context.Context, logs io.Writer, conn *coders return nil } -func holdConnection(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn, holdDur time.Duration, specs []Connection) error { +func holdConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn, holdDur time.Duration, specs []Connection) error { ctx, span := tracing.StartSpan(ctx) defer span.End() @@ -362,7 +364,7 @@ func holdConnection(ctx context.Context, logs io.Writer, conn *codersdk.Workspac return nil } -func agentHTTPClient(conn *codersdk.WorkspaceAgentConn) *http.Client { +func agentHTTPClient(conn *workspacesdk.AgentConn) *http.Client { return &http.Client{ Transport: &http.Transport{ DisableKeepAlives: true, diff --git a/scaletest/createworkspaces/run_test.go b/scaletest/createworkspaces/run_test.go index 88e1e59cd9..486391b7d0 100644 --- a/scaletest/createworkspaces/run_test.go +++ b/scaletest/createworkspaces/run_test.go @@ -19,6 +19,7 @@ import ( "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/scaletest/agentconn" @@ -127,7 +128,7 @@ func Test_Runner(t *testing.T) { }, }, ReconnectingPTY: &reconnectingpty.Config{ - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Height: 24, Width: 80, Command: "echo hello", @@ -416,7 +417,7 @@ func Test_Runner(t *testing.T) { }, }, ReconnectingPTY: &reconnectingpty.Config{ - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Height: 24, Width: 80, Command: "echo hello", diff --git a/scaletest/reconnectingpty/config.go b/scaletest/reconnectingpty/config.go index c226bcc39c..023f817499 100644 --- a/scaletest/reconnectingpty/config.go +++ b/scaletest/reconnectingpty/config.go @@ -7,7 +7,7 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/httpapi" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" ) const ( @@ -23,7 +23,7 @@ type Config struct { // If the ID is not set, defaults to a random UUID. If the width or height // is not set, defaults to 80x24. If the command is not set, defaults to // opening a login shell. Command runs in the default shell. - Init codersdk.WorkspaceAgentReconnectingPTYInit `json:"init"` + Init workspacesdk.AgentReconnectingPTYInit `json:"init"` // Timeout is the duration to wait for the command to exit. Defaults to // 5 minutes. Timeout httpapi.Duration `json:"timeout"` diff --git a/scaletest/reconnectingpty/config_test.go b/scaletest/reconnectingpty/config_test.go index c6944e3268..e0712b4631 100644 --- a/scaletest/reconnectingpty/config_test.go +++ b/scaletest/reconnectingpty/config_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/httpapi" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/scaletest/reconnectingpty" ) @@ -31,7 +31,7 @@ func Test_Config(t *testing.T) { name: "OKFull", config: reconnectingpty.Config{ AgentID: id, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ ID: id, Width: 80, Height: 24, diff --git a/scaletest/reconnectingpty/run.go b/scaletest/reconnectingpty/run.go index d9b01c8a4d..ce66c42f16 100644 --- a/scaletest/reconnectingpty/run.go +++ b/scaletest/reconnectingpty/run.go @@ -15,6 +15,7 @@ import ( "cdr.dev/slog/sloggers/sloghuman" "github.com/coder/coder/v2/coderd/tracing" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/scaletest/harness" "github.com/coder/coder/v2/scaletest/loadtestutil" ) @@ -64,7 +65,7 @@ func (r *Runner) Run(ctx context.Context, _ string, logs io.Writer) error { _, _ = fmt.Fprintf(logs, "\tHeight: %d\n", height) _, _ = fmt.Fprintf(logs, "\tCommand: %q\n\n", r.cfg.Init.Command) - conn, err := r.client.WorkspaceAgentReconnectingPTY(ctx, codersdk.WorkspaceAgentReconnectingPTYOpts{ + conn, err := workspacesdk.New(r.client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ AgentID: r.cfg.AgentID, Reconnect: id, Width: width, diff --git a/scaletest/reconnectingpty/run_test.go b/scaletest/reconnectingpty/run_test.go index 524e2172ab..c740f922fa 100644 --- a/scaletest/reconnectingpty/run_test.go +++ b/scaletest/reconnectingpty/run_test.go @@ -13,6 +13,7 @@ import ( "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/provisioner/echo" "github.com/coder/coder/v2/provisionersdk/proto" "github.com/coder/coder/v2/scaletest/reconnectingpty" @@ -29,7 +30,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ // Use ; here because it's powershell compatible (vs &&). Command: "echo 'hello world'; sleep 1", }, @@ -58,7 +59,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'", }, LogOutput: false, @@ -86,7 +87,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'", }, Timeout: httpapi.Duration(2 * testutil.WaitSuperLong), @@ -110,7 +111,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "sleep 120", }, Timeout: httpapi.Duration(2 * time.Second), @@ -139,7 +140,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "sleep 120", }, Timeout: httpapi.Duration(2 * time.Second), @@ -164,7 +165,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'", }, Timeout: httpapi.Duration(2 * testutil.WaitSuperLong), @@ -194,7 +195,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'; sleep 1", }, ExpectOutput: "hello world", @@ -218,7 +219,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: codersdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'; sleep 1", }, ExpectOutput: "bello borld", diff --git a/scaletest/workspacetraffic/conn.go b/scaletest/workspacetraffic/conn.go index 31dfaf99c7..455feaa8f6 100644 --- a/scaletest/workspacetraffic/conn.go +++ b/scaletest/workspacetraffic/conn.go @@ -13,6 +13,7 @@ import ( "nhooyr.io/websocket" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/google/uuid" gossh "golang.org/x/crypto/ssh" @@ -38,7 +39,7 @@ const ( func connectRPTY(ctx context.Context, client *codersdk.Client, agentID, reconnect uuid.UUID, cmd string) (*countReadWriteCloser, error) { width, height := 80, 25 - conn, err := client.WorkspaceAgentReconnectingPTY(ctx, codersdk.WorkspaceAgentReconnectingPTYOpts{ + conn, err := workspacesdk.New(client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ AgentID: agentID, Reconnect: reconnect, Width: uint16(width), @@ -107,7 +108,7 @@ func (c *rptyConn) writeNoLock(p []byte) (n int, err error) { pp = p[:rptyJSONMaxDataSize] } p = p[len(pp):] - req := codersdk.ReconnectingPTYRequest{Data: string(pp)} + req := workspacesdk.ReconnectingPTYRequest{Data: string(pp)} if err := c.wenc.Encode(req); err != nil { return n, xerrors.Errorf("encode pty request: %w", err) } @@ -156,7 +157,7 @@ func connectSSH(ctx context.Context, client *codersdk.Client, agentID uuid.UUID, } }() - agentConn, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{}) + agentConn, err := workspacesdk.New(client).DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{}) if err != nil { return nil, xerrors.Errorf("dial workspace agent: %w", err) } diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index 4f4c3d8895..6107396eaa 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -28,7 +28,7 @@ import ( var ( // baseDirs are the directories to introspect for types to generate. - baseDirs = [...]string{"./codersdk"} + baseDirs = [...]string{"./codersdk", "./codersdk/healthsdk"} // externalTypes are types that are not in the baseDirs, but we want to // support. These are usually types that are used in the baseDirs. // Do not include things like "Database", as that would break the idea @@ -357,7 +357,7 @@ type Maps struct { // objName prepends the package name of a type if it is outside of codersdk. func objName(obj types.Object) string { - if pkgName := obj.Pkg().Name(); pkgName != "codersdk" { + if pkgName := obj.Pkg().Name(); pkgName != "codersdk" && pkgName != "healthsdk" { return cases.Title(language.English).String(pkgName) + obj.Name() } return obj.Name() @@ -879,7 +879,7 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { return TypescriptType{ValueType: "HealthMessage"}, nil case "github.com/coder/coder/v2/coderd/healthcheck/health.Severity": return TypescriptType{ValueType: "HealthSeverity"}, nil - case "github.com/coder/coder/v2/codersdk.HealthSection": + case "github.com/coder/coder/v2/healthsdk.HealthSection": return TypescriptType{ValueType: "HealthSection"}, nil case "github.com/coder/coder/v2/codersdk.ProvisionerDaemon": return TypescriptType{ValueType: "ProvisionerDaemon"}, nil diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f6dbdf131c..bdf744e104 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -27,19 +27,6 @@ export interface APIKeyWithOwner extends APIKey { readonly username: string; } -// From codersdk/health.go -export interface AccessURLReport { - readonly healthy: boolean; - readonly severity: HealthSeverity; - readonly warnings: HealthMessage[]; - readonly dismissed: boolean; - readonly access_url: string; - readonly reachable: boolean; - readonly status_code: number; - readonly healthz_response: string; - readonly error?: string; -} - // From codersdk/licenses.go export interface AddLicenseRequest { readonly license: string; @@ -358,60 +345,12 @@ export interface DERPConfig { readonly path: string; } -// From codersdk/health.go -export interface DERPHealthReport { - readonly healthy: boolean; - readonly severity: HealthSeverity; - readonly warnings: HealthMessage[]; - readonly dismissed: boolean; - readonly regions: Record; - // Named type "tailscale.com/net/netcheck.Report" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly netcheck?: any; - readonly netcheck_err?: string; - readonly netcheck_logs: string[]; - readonly error?: string; -} - -// From codersdk/health.go -export interface DERPNodeReport { - readonly healthy: boolean; - readonly severity: HealthSeverity; - readonly warnings: HealthMessage[]; - // Named type "tailscale.com/tailcfg.DERPNode" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly node?: any; - // Named type "tailscale.com/derp.ServerInfoMessage" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly node_info: any; - readonly can_exchange_messages: boolean; - readonly round_trip_ping: string; - readonly round_trip_ping_ms: number; - readonly uses_websocket: boolean; - readonly client_logs: string[][]; - readonly client_errs: string[][]; - readonly error?: string; - readonly stun: STUNReport; -} - // From codersdk/workspaceagents.go export interface DERPRegion { readonly preferred: boolean; readonly latency_ms: number; } -// From codersdk/health.go -export interface DERPRegionReport { - readonly healthy: boolean; - readonly severity: HealthSeverity; - readonly warnings: HealthMessage[]; - // Named type "tailscale.com/tailcfg.DERPRegion" unknown, using "any" - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type - readonly region?: any; - readonly node_reports: DERPNodeReport[]; - readonly error?: string; -} - // From codersdk/deployment.go export interface DERPServerConfig { readonly enable: boolean; @@ -429,19 +368,6 @@ export interface DangerousConfig { readonly allow_all_cors: boolean; } -// From codersdk/health.go -export interface DatabaseReport { - readonly healthy: boolean; - readonly severity: HealthSeverity; - readonly warnings: HealthMessage[]; - readonly dismissed: boolean; - readonly reachable: boolean; - readonly latency: string; - readonly latency_ms: number; - readonly threshold_ms: number; - readonly error?: string; -} - // From codersdk/workspaceagentportshare.go export interface DeleteWorkspaceAgentPortShareRequest { readonly agent_name: string; @@ -661,11 +587,6 @@ export interface Group { readonly source: GroupSource; } -// From codersdk/health.go -export interface HealthSettings { - readonly dismissed_healthchecks: HealthSection[]; -} - // From codersdk/workspaceapps.go export interface Healthcheck { readonly url: string; @@ -679,21 +600,6 @@ export interface HealthcheckConfig { readonly threshold_database: number; } -// From codersdk/health.go -export interface HealthcheckReport { - readonly time: string; - readonly healthy: boolean; - readonly severity: HealthSeverity; - readonly failing_sections: HealthSection[]; - readonly derp: DERPHealthReport; - readonly access_url: AccessURLReport; - readonly websocket: WebsocketReport; - readonly database: DatabaseReport; - readonly workspace_proxy: WorkspaceProxyReport; - readonly provisioner_daemons: ProvisionerDaemonsReport; - readonly coder_version: string; -} - // From codersdk/workspaceagents.go export interface IssueReconnectingPTYSignedTokenRequest { readonly url: string; @@ -950,21 +856,6 @@ export interface ProvisionerDaemon { readonly tags: Record; } -// From codersdk/health.go -export interface ProvisionerDaemonsReport { - readonly severity: HealthSeverity; - readonly warnings: HealthMessage[]; - readonly dismissed: boolean; - readonly error?: string; - readonly items: ProvisionerDaemonsReportItem[]; -} - -// From codersdk/health.go -export interface ProvisionerDaemonsReportItem { - readonly provisioner_daemon: ProvisionerDaemon; - readonly warnings: HealthMessage[]; -} - // From codersdk/provisionerdaemons.go export interface ProvisionerJob { readonly id: string; @@ -1084,13 +975,6 @@ export interface SSHConfigResponse { readonly ssh_config_options: Record; } -// From codersdk/health.go -export interface STUNReport { - readonly Enabled: boolean; - readonly CanSTUN: boolean; - readonly Error?: string; -} - // From codersdk/serversentevents.go export interface ServerSentEvent { readonly type: ServerSentEventType; @@ -1399,11 +1283,6 @@ export interface UpdateCheckResponse { readonly url: string; } -// From codersdk/health.go -export interface UpdateHealthSettings { - readonly dismissed_healthchecks: HealthSection[]; -} - // From codersdk/users.go export interface UpdateRoles { readonly roles: string[]; @@ -1619,17 +1498,6 @@ export interface VariableValue { readonly value: string; } -// From codersdk/health.go -export interface WebsocketReport { - readonly healthy: boolean; - readonly severity: HealthSeverity; - readonly warnings: string[]; - readonly dismissed: boolean; - readonly body: string; - readonly code: number; - readonly error?: string; -} - // From codersdk/workspaces.go export interface Workspace { readonly id: string; @@ -1702,14 +1570,14 @@ export interface WorkspaceAgentHealth { readonly reason?: string; } -// From codersdk/workspaceagentconn.go +// From codersdk/workspaceagents.go export interface WorkspaceAgentListeningPort { readonly process_name: string; readonly network: string; readonly port: number; } -// From codersdk/workspaceagentconn.go +// From codersdk/workspaceagents.go export interface WorkspaceAgentListeningPortsResponse { readonly ports: WorkspaceAgentListeningPort[]; } @@ -1884,16 +1752,6 @@ export interface WorkspaceProxyBuildInfo { readonly dashboard_url: string; } -// From codersdk/health.go -export interface WorkspaceProxyReport { - readonly healthy: boolean; - readonly severity: HealthSeverity; - readonly warnings: HealthMessage[]; - readonly dismissed: boolean; - readonly error?: string; - readonly workspace_proxies: RegionsResponse; -} - // From codersdk/workspaceproxy.go export interface WorkspaceProxyStatus { readonly status: ProxyHealthStatus; @@ -2080,23 +1938,6 @@ export const FeatureNames: FeatureName[] = [ export type GroupSource = "oidc" | "user"; export const GroupSources: GroupSource[] = ["oidc", "user"]; -// From codersdk/health.go -export type HealthSection = - | "AccessURL" - | "DERP" - | "Database" - | "ProvisionerDaemons" - | "Websocket" - | "WorkspaceProxy"; -export const HealthSections: HealthSection[] = [ - "AccessURL", - "DERP", - "Database", - "ProvisionerDaemons", - "Websocket", - "WorkspaceProxy", -]; - // From codersdk/insights.go export type InsightsReportInterval = "day" | "week"; export const InsightsReportIntervals: InsightsReportInterval[] = [ @@ -2433,6 +2274,167 @@ export const WorkspaceTransitions: WorkspaceTransition[] = [ // From codersdk/workspaceproxy.go export type RegionTypes = Region | WorkspaceProxy; +// The code below is generated from codersdk/healthsdk. + +// From healthsdk/healthsdk.go +export interface AccessURLReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + readonly dismissed: boolean; + readonly access_url: string; + readonly reachable: boolean; + readonly status_code: number; + readonly healthz_response: string; + readonly error?: string; +} + +// From healthsdk/healthsdk.go +export interface DERPHealthReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + readonly dismissed: boolean; + readonly regions: Record; + // Named type "tailscale.com/net/netcheck.Report" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly netcheck?: any; + readonly netcheck_err?: string; + readonly netcheck_logs: string[]; + readonly error?: string; +} + +// From healthsdk/healthsdk.go +export interface DERPNodeReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + // Named type "tailscale.com/tailcfg.DERPNode" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly node?: any; + // Named type "tailscale.com/derp.ServerInfoMessage" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly node_info: any; + readonly can_exchange_messages: boolean; + readonly round_trip_ping: string; + readonly round_trip_ping_ms: number; + readonly uses_websocket: boolean; + readonly client_logs: string[][]; + readonly client_errs: string[][]; + readonly error?: string; + readonly stun: STUNReport; +} + +// From healthsdk/healthsdk.go +export interface DERPRegionReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + // Named type "tailscale.com/tailcfg.DERPRegion" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type + readonly region?: any; + readonly node_reports: DERPNodeReport[]; + readonly error?: string; +} + +// From healthsdk/healthsdk.go +export interface DatabaseReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + readonly dismissed: boolean; + readonly reachable: boolean; + readonly latency: string; + readonly latency_ms: number; + readonly threshold_ms: number; + readonly error?: string; +} + +// From healthsdk/healthsdk.go +export interface HealthSettings { + readonly dismissed_healthchecks: HealthSection[]; +} + +// From healthsdk/healthsdk.go +export interface HealthcheckReport { + readonly time: string; + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly failing_sections: HealthSection[]; + readonly derp: DERPHealthReport; + readonly access_url: AccessURLReport; + readonly websocket: WebsocketReport; + readonly database: DatabaseReport; + readonly workspace_proxy: WorkspaceProxyReport; + readonly provisioner_daemons: ProvisionerDaemonsReport; + readonly coder_version: string; +} + +// From healthsdk/healthsdk.go +export interface ProvisionerDaemonsReport { + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + readonly dismissed: boolean; + readonly error?: string; + readonly items: ProvisionerDaemonsReportItem[]; +} + +// From healthsdk/healthsdk.go +export interface ProvisionerDaemonsReportItem { + readonly provisioner_daemon: ProvisionerDaemon; + readonly warnings: HealthMessage[]; +} + +// From healthsdk/healthsdk.go +export interface STUNReport { + readonly Enabled: boolean; + readonly CanSTUN: boolean; + readonly Error?: string; +} + +// From healthsdk/healthsdk.go +export interface UpdateHealthSettings { + readonly dismissed_healthchecks: HealthSection[]; +} + +// From healthsdk/healthsdk.go +export interface WebsocketReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: string[]; + readonly dismissed: boolean; + readonly body: string; + readonly code: number; + readonly error?: string; +} + +// From healthsdk/healthsdk.go +export interface WorkspaceProxyReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + readonly dismissed: boolean; + readonly error?: string; + readonly workspace_proxies: RegionsResponse; +} + +// From healthsdk/healthsdk.go +export type HealthSection = + | "AccessURL" + | "DERP" + | "Database" + | "ProvisionerDaemons" + | "Websocket" + | "WorkspaceProxy"; +export const HealthSections: HealthSection[] = [ + "AccessURL", + "DERP", + "Database", + "ProvisionerDaemons", + "Websocket", + "WorkspaceProxy", +]; + // The code below is generated from coderd/healthcheck/health. // From health/model.go diff --git a/support/support.go b/support/support.go index 1cf493a292..47cad76a7d 100644 --- a/support/support.go +++ b/support/support.go @@ -21,6 +21,8 @@ import ( "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" + "github.com/coder/coder/v2/codersdk/healthsdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/tailnet" ) @@ -37,16 +39,16 @@ type Bundle struct { } type Deployment struct { - BuildInfo *codersdk.BuildInfoResponse `json:"build"` - Config *codersdk.DeploymentConfig `json:"config"` - Experiments codersdk.Experiments `json:"experiments"` - HealthReport *codersdk.HealthcheckReport `json:"health_report"` + BuildInfo *codersdk.BuildInfoResponse `json:"build"` + Config *codersdk.DeploymentConfig `json:"config"` + Experiments codersdk.Experiments `json:"experiments"` + HealthReport *healthsdk.HealthcheckReport `json:"health_report"` } type Network struct { - CoordinatorDebug string `json:"coordinator_debug"` - TailnetDebug string `json:"tailnet_debug"` - Netcheck *codersdk.WorkspaceAgentConnectionInfo `json:"netcheck"` + CoordinatorDebug string `json:"coordinator_debug"` + TailnetDebug string `json:"tailnet_debug"` + Netcheck *workspacesdk.AgentConnectionInfo `json:"netcheck"` } type Workspace struct { @@ -110,7 +112,7 @@ func DeploymentInfo(ctx context.Context, client *codersdk.Client, log slog.Logge }) eg.Go(func() error { - hr, err := client.DebugHealth(ctx) + hr, err := healthsdk.New(client).DebugHealth(ctx) if err != nil { return xerrors.Errorf("fetch health report: %w", err) } @@ -173,7 +175,7 @@ func NetworkInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, log.Warn(ctx, "agent id required for agent connection info") return nil } - connInfo, err := client.WorkspaceAgentConnectionInfo(ctx, agentID) + connInfo, err := workspacesdk.New(client).AgentConnectionInfo(ctx, agentID) if err != nil { return xerrors.Errorf("fetch agent conn info: %w", err) } @@ -331,10 +333,11 @@ func AgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, ag } 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, - }) + conn, err := workspacesdk.New(client). + DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ + Logger: log.Named("dial-agent"), + BlockEndpoints: false, + }) closer = func() {} diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index 1d6a265d42..d8a6f297b5 100644 --- a/tailnet/coordinator_test.go +++ b/tailnet/coordinator_test.go @@ -22,7 +22,7 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" - "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/tailnet/proto" "github.com/coder/coder/v2/tailnet/tailnettest" @@ -155,7 +155,7 @@ func TestCoordinator(t *testing.T) { }() sendNode(&tailnet.Node{ Addresses: []netip.Prefix{ - netip.PrefixFrom(codersdk.WorkspaceAgentIP, 128), + netip.PrefixFrom(workspacesdk.AgentIP, 128), }, PreferredDERP: 10, })