From 54b53dd7b803c3c432d45315f5421c7f74fdb1dc Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Sun, 24 Mar 2024 21:50:38 +0000 Subject: [PATCH 01/11] chore(codersdk): move all wireguard functions to `workspacesdk` Currently, importing `codersdk` just to interact with the API requires importing tailscale, which causes builds to fail unless manually using our fork. --- agent/agent.go | 18 +- agent/agent_test.go | 18 +- agent/ports_supported.go | 3 +- agent/reconnectingpty/reconnectingpty.go | 5 +- cli/agent_test.go | 9 +- cli/configssh_test.go | 4 +- cli/exp_scaletest.go | 3 +- cli/netcheck.go | 3 +- cli/ping.go | 10 +- cli/portforward.go | 12 +- cli/speedtest.go | 8 +- cli/ssh.go | 10 +- cli/support_test.go | 3 +- cli/vscodessh.go | 12 +- coderd/activitybump_test.go | 15 +- coderd/coderd_test.go | 8 +- coderd/insights_test.go | 22 +- .../insights/metricscollector_test.go | 8 +- coderd/tailnet.go | 10 +- coderd/tailnet_test.go | 12 +- coderd/templates_test.go | 8 +- coderd/workspaceagents.go | 13 +- coderd/workspaceagents_test.go | 44 +- coderd/workspaceapps/apptest/apptest.go | 11 +- coderd/workspaceapps/proxy.go | 9 +- codersdk/health.go | 3 +- codersdk/workspaceagents.go | 471 ++---------------- .../{ => workspacesdk}/workspaceagentconn.go | 33 +- codersdk/workspacesdk/workspaceagents.go | 455 +++++++++++++++++ .../workspaceagents_internal_test.go | 5 +- .../workspaceagents_test.go | 2 +- enterprise/coderd/coderd_test.go | 3 +- enterprise/coderd/replicas_test.go | 19 +- enterprise/coderd/workspaceagents_test.go | 5 +- enterprise/tailnet/pgcoord_test.go | 4 +- enterprise/wsproxy/wsproxy_test.go | 20 +- enterprise/wsproxy/wsproxysdk/wsproxysdk.go | 5 +- scaletest/agentconn/run.go | 26 +- scaletest/createworkspaces/run_test.go | 5 +- scaletest/reconnectingpty/config.go | 4 +- scaletest/reconnectingpty/config_test.go | 4 +- scaletest/reconnectingpty/run.go | 3 +- scaletest/reconnectingpty/run_test.go | 17 +- scaletest/workspacetraffic/conn.go | 7 +- support/support.go | 18 +- tailnet/coordinator_test.go | 4 +- 46 files changed, 738 insertions(+), 653 deletions(-) rename codersdk/{ => workspacesdk}/workspaceagentconn.go (93%) create mode 100644 codersdk/workspacesdk/workspaceagents.go rename codersdk/{ => workspacesdk}/workspaceagents_internal_test.go (95%) rename codersdk/{ => workspacesdk}/workspaceagents_test.go (97%) diff --git a/agent/agent.go b/agent/agent.go index 6f601cbcd7742..2f7102c190ae2 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.WorkspaceAgentIP, 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.WorkspaceAgentSSHPort)) 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.WorkspaceAgentReconnectingPTYPort)) 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.WorkspaceAgentReconnectingPTYInit 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.WorkspaceAgentSpeedtestPort)) 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.WorkspaceAgentHTTPAPIServerPort)) 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.WorkspaceAgentReconnectingPTYInit, 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 07258fee5eef3..25db07864f6f2 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.WorkspaceAgentConn { 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.NewWorkspaceAgentConn(conn, workspacesdk.WorkspaceAgentConnOptions{ 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.WorkspaceAgentConn, *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.NewWorkspaceAgentConn(conn, workspacesdk.WorkspaceAgentConnOptions{ AgentID: metadata.AgentID, }) t.Cleanup(func() { diff --git a/agent/ports_supported.go b/agent/ports_supported.go index c6d7b3406255b..079eaa5c92d30 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.WorkspaceAgentMinimumListeningPort { continue } diff --git a/agent/reconnectingpty/reconnectingpty.go b/agent/reconnectingpty/reconnectingpty.go index 280cf62aaa841..fffe199f59b54 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 fd71b962b926d..25551ff203303 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.NewWorkspaceClient(client). + DialWorkspaceAgent(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.NewWorkspaceClient(client). + DialWorkspaceAgent(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.NewWorkspaceClient(client).DialWorkspaceAgent(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 ee66e350c1582..05e98cf7d4512 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.NewWorkspaceClient(client). + DialWorkspaceAgent(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 e92c3b5eee8f9..6ced0e3a95a3f 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.WorkspaceAgentReconnectingPTYInit{ ID: uuid.Nil, Height: 24, Width: 80, diff --git a/cli/netcheck.go b/cli/netcheck.go index 9035870e3f64f..89840948f8254 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.NewWorkspaceClient(client).WorkspaceAgentConnectionInfoGeneric(ctx) if err != nil { return err } diff --git a/cli/ping.go b/cli/ping.go index 34f06d166b90e..faa2b058522ed 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + Logger: logger, + BlockEndpoints: r.disableDirect, + }) if err != nil { return err } diff --git a/cli/portforward.go b/cli/portforward.go index ebe925a6a3801..7d6b55e117f64 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.WorkspaceAgentConn, wg *sync.WaitGroup, spec portForwardSpec, logger slog.Logger, diff --git a/cli/speedtest.go b/cli/speedtest.go index 20697295feaea..29368138f1e9f 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + Logger: logger, + }) if err != nil { return err } diff --git a/cli/ssh.go b/cli/ssh.go index 4e0f1799ee9a0..78d22c3067db4 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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 cad0457d2ab3d..fb2fb8a0ff3c9 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -25,6 +25,7 @@ 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/provisionersdk/proto" "github.com/coder/coder/v2/tailnet" "github.com/coder/coder/v2/testutil" @@ -165,7 +166,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.WorkspaceAgentConnectionInfo 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 b5c2c7d352945..be4cfc1def6fa 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.WorkspaceAgentConn, 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 2f54a598e689d..d318cbd8a55f4 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + Logger: slogtest.Make(t, nil), + }) require.NoError(t, err) defer conn.Close() diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index a632b399e8dea..34f821d89b7cc 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.NewWorkspaceClient(client) user := coderdtest.CreateFirstUser(t, client) - gen, err := client.WorkspaceAgentConnectionInfoGeneric(context.Background()) + gen, err := wsclient.WorkspaceAgentConnectionInfoGeneric(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.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, + &workspacesdk.DialWorkspaceAgentOptions{ Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug).Named("client"), }, ) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 99f91323f0109..5a0de7cc78ef6 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + Logger: logger.Named("client"), + }) require.NoError(t, err) defer conn.Close() diff --git a/coderd/prometheusmetrics/insights/metricscollector_test.go b/coderd/prometheusmetrics/insights/metricscollector_test.go index 47591815a52bc..d90453daad0e9 100644 --- a/coderd/prometheusmetrics/insights/metricscollector_test.go +++ b/coderd/prometheusmetrics/insights/metricscollector_test.go @@ -26,6 +26,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" @@ -129,9 +130,10 @@ func TestCollectInsights(t *testing.T) { defer closeFunc() // 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + Logger: logger.Named("client"), + }) require.NoError(t, err) defer conn.Close() diff --git a/coderd/tailnet.go b/coderd/tailnet.go index 4fbae175bf635..170770fa96efc 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.WorkspaceAgentConn, func(), error) { var ( - conn *codersdk.WorkspaceAgentConn + conn *workspacesdk.WorkspaceAgentConn 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.NewWorkspaceAgentConn(s.conn, workspacesdk.WorkspaceAgentConnOptions{ 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 51fdae5ea2f12..20d2ee56b7700 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.WorkspaceAgentHTTPAPIServerPort)) 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.WorkspaceAgentHTTPAPIServerPort)) 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.WorkspaceAgentHTTPAPIServerPort)) 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.WorkspaceAgentHTTPAPIServerPort), 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.WorkspaceAgentHTTPAPIServerPort)) require.NoError(t, err) rp := serverTailnet.ReverseProxy(u, u, a.id) diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 13501274f5712..12474d9665934 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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 cf64d92d28b1e..2f24e90c80eb4 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.WorkspaceAgentMinimumListeningPort { continue } if _, ok := appPorts[port.Port]; ok { continue } - if _, ok := codersdk.WorkspaceAgentIgnoredListeningPorts[port.Port]; ok { + if _, ok := workspacesdk.WorkspaceAgentIgnoredListeningPorts[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.WorkspaceAgentConnectionInfo // @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.WorkspaceAgentConnectionInfo{ 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.WorkspaceAgentConnectionInfo // @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.WorkspaceAgentConnectionInfo{ 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 68a1492e79533..7352e30dc2322 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.NewWorkspaceClient(client). + DialWorkspaceAgent(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.WorkspaceAgentConn, 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.WorkspaceAgentConnectionInfo 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.WorkspaceAgentMinimumListeningPort || port > 65535 { return true } - if _, ok := codersdk.WorkspaceAgentIgnoredListeningPorts[uint16(port)]; ok { + if _, ok := workspacesdk.WorkspaceAgentIgnoredListeningPorts[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.WorkspaceAgentIgnoredListeningPorts { 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.WorkspaceAgentConn, 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{ + 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 c5327138940d2..d8b4a476dbb51 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.WorkspaceAgentMinimumListeningPort - 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.NewWorkspaceClient(client).WorkspaceAgentReconnectingPTY(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 70d8a64efa429..94a090375e572 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.WorkspaceAgentConn, 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.WorkspaceAgentMinimumListeningPort { 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.WorkspaceAgentMinimumListeningPort, + ), }) return } diff --git a/codersdk/health.go b/codersdk/health.go index faa252464eb9e..1c4da7e53f92a 100644 --- a/codersdk/health.go +++ b/codersdk/health.go @@ -6,13 +6,12 @@ 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" ) type HealthSection string diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 7a8b2e5d3b3d5..6eab57bb3c4ad 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"` -} - -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) - } - - var connInfo WorkspaceAgentConnectionInfo - return connInfo, json.NewDecoder(res.Body).Decode(&connInfo) -} - -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 +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"` } -// 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() -} +type AgentSubsystem string -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)) - } - } -} +const ( + AgentSubsystemEnvbox AgentSubsystem = "envbox" + AgentSubsystemEnvbuilder AgentSubsystem = "envbuilder" + AgentSubsystemExectrace AgentSubsystem = "exectrace" +) -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/workspaceagentconn.go similarity index 93% rename from codersdk/workspaceagentconn.go rename to codersdk/workspacesdk/workspaceagentconn.go index cf85ee07de2a2..e8a1a359c06df 100644 --- a/codersdk/workspaceagentconn.go +++ b/codersdk/workspacesdk/workspaceagentconn.go @@ -1,4 +1,4 @@ -package codersdk +package workspacesdk import ( "context" @@ -23,6 +23,7 @@ import ( "tailscale.com/net/speedtest" "github.com/coder/coder/v2/coderd/tracing" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/tailnet" ) @@ -325,34 +326,20 @@ 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 *WorkspaceAgentConn) 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) } @@ -365,7 +352,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) @@ -386,7 +373,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 { @@ -405,7 +392,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 { @@ -424,7 +411,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 { diff --git a/codersdk/workspacesdk/workspaceagents.go b/codersdk/workspacesdk/workspaceagents.go new file mode 100644 index 0000000000000..ab26bc216e70d --- /dev/null +++ b/codersdk/workspacesdk/workspaceagents.go @@ -0,0 +1,455 @@ +package workspacesdk + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/http/cookiejar" + "net/netip" + "strconv" + "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" +) + +type WorkspaceClient struct { + client *codersdk.Client +} + +func NewWorkspaceClient(c *codersdk.Client) *WorkspaceClient { + return &WorkspaceClient{client: c} +} + +// 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"` +} + +func (c *WorkspaceClient) WorkspaceAgentConnectionInfoGeneric(ctx context.Context) (WorkspaceAgentConnectionInfo, error) { + res, err := c.client.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{}, codersdk.ReadBodyAsError(res) + } + + var connInfo WorkspaceAgentConnectionInfo + return connInfo, json.NewDecoder(res.Body).Decode(&connInfo) +} + +func (c *WorkspaceClient) WorkspaceAgentConnectionInfo(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentConnectionInfo, error) { + res, err := c.client.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{}, codersdk.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 *WorkspaceClient) 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.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 = 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 = 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) + } +} + +// @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 +} + +// 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 *WorkspaceClient) WorkspaceAgentReconnectingPTY(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/workspaceagents_internal_test.go similarity index 95% rename from codersdk/workspaceagents_internal_test.go rename to codersdk/workspacesdk/workspaceagents_internal_test.go index 0228cee1e2a52..57e6f751ff840 100644 --- a/codersdk/workspaceagents_internal_test.go +++ b/codersdk/workspacesdk/workspaceagents_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/workspaceagents_test.go similarity index 97% rename from codersdk/workspaceagents_test.go rename to codersdk/workspacesdk/workspaceagents_test.go index 31a516bfdd96f..317db4471319f 100644 --- a/codersdk/workspaceagents_test.go +++ b/codersdk/workspacesdk/workspaceagents_test.go @@ -1,4 +1,4 @@ -package codersdk_test +package workspacesdk_test import ( "net/url" diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index 73b8cca5ac828..378bebff1b214 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -24,6 +24,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" @@ -237,7 +238,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.NewWorkspaceClient(client).DialWorkspaceAgent(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 6d348db782711..d2c111e70099f 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.NewWorkspaceClient(secondClient). + DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.NewWorkspaceClient(secondClient). + DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + 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 a6cf84a59404b..eb6408e53b924 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.NewWorkspaceClient(client).DialWorkspaceAgent(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.NewWorkspaceClient(client).DialWorkspaceAgent(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 c11cc9b630c1e..33c4813821579 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.WorkspaceAgentIP, 128), }, PreferredDERP: 10, }) diff --git a/enterprise/wsproxy/wsproxy_test.go b/enterprise/wsproxy/wsproxy_test.go index c18cd0413feb4..4b5133aa81615 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.NewWorkspaceClient(client).WorkspaceAgentConnectionInfo(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.NewWorkspaceClient(client).WorkspaceAgentConnectionInfo(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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{ + 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 37636102bb413..84454b7b8eb9d 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.DialWorkspaceAgentOptions) (agentConn *workspacesdk.WorkspaceAgentConn, err error) { + return workspacesdk.NewWorkspaceClient(c.SDKClient).DialWorkspaceAgent(ctx, agentID, options) } type IssueSignedAppTokenResponse struct { diff --git a/scaletest/agentconn/run.go b/scaletest/agentconn/run.go index cc942448ff6d4..4e328a683e1bd 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.NewWorkspaceClient(r.client). + DialWorkspaceAgent(ctx, r.cfg.AgentID, &workspacesdk.DialWorkspaceAgentOptions{ + 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.WorkspaceAgentConn) 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.WorkspaceAgentConn) 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.WorkspaceAgentConn) 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.WorkspaceAgentHTTPAPIServerPort)), 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.WorkspaceAgentConn, 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.WorkspaceAgentConn, 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.WorkspaceAgentConn) *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 88e1e59cd9f37..e475e3a7d54d9 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.WorkspaceAgentReconnectingPTYInit{ 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.WorkspaceAgentReconnectingPTYInit{ Height: 24, Width: 80, Command: "echo hello", diff --git a/scaletest/reconnectingpty/config.go b/scaletest/reconnectingpty/config.go index c226bcc39ca45..a49f7f3d25e09 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.WorkspaceAgentReconnectingPTYInit `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 c6944e3268076..07adfc8816c31 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.WorkspaceAgentReconnectingPTYInit{ ID: id, Width: 80, Height: 24, diff --git a/scaletest/reconnectingpty/run.go b/scaletest/reconnectingpty/run.go index d9b01c8a4d82a..57060907d40f2 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.NewWorkspaceClient(r.client).WorkspaceAgentReconnectingPTY(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 524e2172ab447..b87a66af662cf 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.WorkspaceAgentReconnectingPTYInit{ // 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.WorkspaceAgentReconnectingPTYInit{ 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.WorkspaceAgentReconnectingPTYInit{ 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.WorkspaceAgentReconnectingPTYInit{ 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.WorkspaceAgentReconnectingPTYInit{ 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.WorkspaceAgentReconnectingPTYInit{ 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.WorkspaceAgentReconnectingPTYInit{ 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.WorkspaceAgentReconnectingPTYInit{ Command: "echo 'hello world'; sleep 1", }, ExpectOutput: "bello borld", diff --git a/scaletest/workspacetraffic/conn.go b/scaletest/workspacetraffic/conn.go index 31dfaf99c76bd..3edf14915ce3e 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.NewWorkspaceClient(client).WorkspaceAgentReconnectingPTY(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.NewWorkspaceClient(client).DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{}) if err != nil { return nil, xerrors.Errorf("dial workspace agent: %w", err) } diff --git a/support/support.go b/support/support.go index 1cf493a292129..0c0021783a275 100644 --- a/support/support.go +++ b/support/support.go @@ -21,6 +21,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" ) @@ -44,9 +45,9 @@ type Deployment struct { } 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.WorkspaceAgentConnectionInfo `json:"netcheck"` } type Workspace struct { @@ -173,7 +174,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.NewWorkspaceClient(client).WorkspaceAgentConnectionInfo(ctx, agentID) if err != nil { return xerrors.Errorf("fetch agent conn info: %w", err) } @@ -331,10 +332,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.NewWorkspaceClient(client). + DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{ + Logger: log.Named("dial-agent"), + BlockEndpoints: false, + }) closer = func() {} diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index 1d6a265d4247f..c1e06314eff8a 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.WorkspaceAgentIP, 128), }, PreferredDERP: 10, }) From 6a8d8713d5e3c0712f1a88c08be3e65d9743f39d Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Sun, 24 Mar 2024 22:21:48 +0000 Subject: [PATCH 02/11] move health into healthsdk it imports tailscale --- cli/netcheck_test.go | 4 +- cli/support_test.go | 3 +- coderd/coderd.go | 12 +- coderd/coderdtest/coderdtest.go | 3 +- coderd/database/types.go | 6 +- coderd/debug.go | 36 ++--- coderd/debug_test.go | 68 ++++----- coderd/healthcheck/accessurl.go | 4 +- coderd/healthcheck/database.go | 4 +- coderd/healthcheck/derphealth/derp.go | 20 +-- coderd/healthcheck/healthcheck.go | 56 +++---- coderd/healthcheck/healthcheck_test.go | 194 ++++++++++++------------- coderd/healthcheck/provisioner.go | 8 +- coderd/healthcheck/provisioner_test.go | 30 ++-- coderd/healthcheck/websocket.go | 4 +- coderd/healthcheck/workspaceproxy.go | 3 +- codersdk/{ => healthsdk}/health.go | 35 +++-- scripts/apitypings/main.go | 2 +- support/support.go | 11 +- 19 files changed, 258 insertions(+), 245 deletions(-) rename codersdk/{ => healthsdk}/health.go (86%) diff --git a/cli/netcheck_test.go b/cli/netcheck_test.go index a2004a22242bc..45166861db04f 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/support_test.go b/cli/support_test.go index fb2fb8a0ff3c9..5f8c928135acc 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -25,6 +25,7 @@ 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" @@ -156,7 +157,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": diff --git a/coderd/coderd.go b/coderd/coderd.go index 7c62a62bc20dc..102f4ddb42613 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" @@ -147,7 +147,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] @@ -417,7 +417,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"), @@ -456,7 +456,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{ @@ -1201,8 +1201,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/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 60bec647874fe..b3288c84b8988 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 2beb74c868a1f..b9f195e541c20 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 da40167c9cac5..7cef04640d6c2 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) @@ -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 b3ba003755738..07b2bacf8de99 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.NewHealthClient(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.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // then - settings, err := adminClient.HealthSettings(ctx) + settings, err := healthsdk.NewHealthClient(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.NewHealthClient(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.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // then - settings, err := adminClient.HealthSettings(ctx) + settings, err := healthsdk.NewHealthClient(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.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // when - err = adminClient.PutHealthSettings(ctx, expected) + err = healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) // then require.Error(t, err) diff --git a/coderd/healthcheck/accessurl.go b/coderd/healthcheck/accessurl.go index 53ca693a9b8ad..2115285a30ace 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 5d8455da87c05..275124c5b1808 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 d3f99fbed095e..e72c527d700c4 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 0b058dea35663..c724347721335 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 01cd08fb7afa4..bb5cd581dbf9c 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 f30f067fb5c9b..b15899dc099c2 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 7942f5a7ddc50..37530f9f8c747 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 582a5023ef09c..75ee3cdf05c58 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 5302eea76ea4b..d9fdfd5295d6f 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/codersdk/health.go b/codersdk/healthsdk/health.go similarity index 86% rename from codersdk/health.go rename to codersdk/healthsdk/health.go index 1c4da7e53f92a..a0ef1e39e182e 100644 --- a/codersdk/health.go +++ b/codersdk/healthsdk/health.go @@ -1,4 +1,4 @@ -package codersdk +package healthsdk import ( "context" @@ -12,8 +12,17 @@ import ( "tailscale.com/tailcfg" "github.com/coder/coder/v2/coderd/healthcheck/health" + "github.com/coder/coder/v2/codersdk" ) +type HealthClient struct { + client *codersdk.Client +} + +func NewHealthClient(c *codersdk.Client) *HealthClient { + return &HealthClient{client: c} +} + type HealthSection string // If you add another const below, make sure to add it to HealthSections! @@ -43,34 +52,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 } @@ -80,7 +89,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 } @@ -198,8 +207,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 { @@ -221,5 +230,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/scripts/apitypings/main.go b/scripts/apitypings/main.go index 4f4c3d889519b..3093e268d3f24 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -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/support/support.go b/support/support.go index 0c0021783a275..294374a8cee54 100644 --- a/support/support.go +++ b/support/support.go @@ -21,6 +21,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/healthsdk" "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/coder/v2/tailnet" ) @@ -38,10 +39,10 @@ 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 { @@ -111,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.NewHealthClient(client).DebugHealth(ctx) if err != nil { return xerrors.Errorf("fetch health report: %w", err) } From 40b0bc616b34081a11c8045f38bd6cd7fdbbf918 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Sun, 24 Mar 2024 22:29:08 +0000 Subject: [PATCH 03/11] `make gen` --- coderd/apidoc/docs.go | 1024 ++-- coderd/apidoc/swagger.json | 1010 ++-- coderd/debug.go | 4 +- docs/api/agents.md | 6 +- docs/api/debug.md | 24 +- docs/api/schemas.md | 8368 ++++++++++++++++---------------- site/src/api/typesGenerated.ts | 163 +- 7 files changed, 5220 insertions(+), 5379 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index c6c8be1d58c0e..1c80ab3a3a036 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.WorkspaceAgentConnectionInfo" } } }, @@ -6088,7 +6088,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.WorkspaceAgentConnectionInfo" } } } @@ -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,46 +13470,511 @@ const docTemplate = `{ "SeverityError" ] }, - "key.NodePublic": { - "type": "object" - }, - "netcheck.Report": { + "healthsdk.AccessURLReport": { "type": "object", "properties": { - "captivePortal": { - "description": "CaptivePortal is set when we think there's a captive portal that is\nintercepting HTTP traffic.", - "type": "string" - }, - "globalV4": { - "description": "ip:port of global IPv4", + "access_url": { "type": "string" }, - "globalV6": { - "description": "[ip]:port of global IPv6", - "type": "string" + "dismissed": { + "type": "boolean" }, - "hairPinning": { - "description": "HairPinning is whether the router supports communicating\nbetween two local devices through the NATted public IP address\n(on IPv4).", + "error": { "type": "string" }, - "icmpv4": { - "description": "an ICMPv4 round trip completed", + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use ` + "`" + `Severity` + "`" + ` instead.", "type": "boolean" }, - "ipv4": { - "description": "an IPv4 STUN round trip completed", - "type": "boolean" + "healthz_response": { + "type": "string" }, - "ipv4CanSend": { - "description": "an IPv4 packet was able to be sent", + "reachable": { "type": "boolean" }, - "ipv6": { - "description": "an IPv6 STUN round trip completed", - "type": "boolean" + "severity": { + "enum": [ + "ok", + "warning", + "error" + ], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] }, - "ipv6CanSend": { - "description": "an IPv6 packet was able to be sent", + "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" + }, + "netcheck.Report": { + "type": "object", + "properties": { + "captivePortal": { + "description": "CaptivePortal is set when we think there's a captive portal that is\nintercepting HTTP traffic.", + "type": "string" + }, + "globalV4": { + "description": "ip:port of global IPv4", + "type": "string" + }, + "globalV6": { + "description": "[ip]:port of global IPv6", + "type": "string" + }, + "hairPinning": { + "description": "HairPinning is whether the router supports communicating\nbetween two local devices through the NATted public IP address\n(on IPv4).", + "type": "string" + }, + "icmpv4": { + "description": "an ICMPv4 round trip completed", + "type": "boolean" + }, + "ipv4": { + "description": "an IPv4 STUN round trip completed", + "type": "boolean" + }, + "ipv4CanSend": { + "description": "an IPv4 packet was able to be sent", + "type": "boolean" + }, + "ipv6": { + "description": "an IPv6 STUN round trip completed", + "type": "boolean" + }, + "ipv6CanSend": { + "description": "an IPv6 packet was able to be sent", "type": "boolean" }, "mappingVariesByDestIP": { @@ -14490,6 +14476,20 @@ const docTemplate = `{ } } }, + "workspacesdk.WorkspaceAgentConnectionInfo": { + "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 605d9f8c762d2..25cef3afafcc6 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.WorkspaceAgentConnectionInfo" } } }, @@ -5363,7 +5363,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/codersdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.WorkspaceAgentConnectionInfo" } } } @@ -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,80 +12234,517 @@ "enum": ["ok", "warning", "error"], "x-enum-varnames": ["SeverityOK", "SeverityWarning", "SeverityError"] }, - "key.NodePublic": { - "type": "object" - }, - "netcheck.Report": { + "healthsdk.AccessURLReport": { "type": "object", "properties": { - "captivePortal": { - "description": "CaptivePortal is set when we think there's a captive portal that is\nintercepting HTTP traffic.", - "type": "string" - }, - "globalV4": { - "description": "ip:port of global IPv4", + "access_url": { "type": "string" }, - "globalV6": { - "description": "[ip]:port of global IPv6", - "type": "string" + "dismissed": { + "type": "boolean" }, - "hairPinning": { - "description": "HairPinning is whether the router supports communicating\nbetween two local devices through the NATted public IP address\n(on IPv4).", + "error": { "type": "string" }, - "icmpv4": { - "description": "an ICMPv4 round trip completed", + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, - "ipv4": { - "description": "an IPv4 STUN round trip completed", - "type": "boolean" + "healthz_response": { + "type": "string" }, - "ipv4CanSend": { - "description": "an IPv4 packet was able to be sent", + "reachable": { "type": "boolean" }, - "ipv6": { - "description": "an IPv6 STUN round trip completed", - "type": "boolean" + "severity": { + "enum": ["ok", "warning", "error"], + "allOf": [ + { + "$ref": "#/definitions/health.Severity" + } + ] }, - "ipv6CanSend": { - "description": "an IPv6 packet was able to be sent", + "status_code": { + "type": "integer" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/definitions/health.Message" + } + } + } + }, + "healthsdk.DERPHealthReport": { + "type": "object", + "properties": { + "dismissed": { "type": "boolean" }, - "mappingVariesByDestIP": { - "description": "MappingVariesByDestIP is whether STUN results depend which\nSTUN server you're talking to (on IPv4).", + "error": { "type": "string" }, - "oshasIPv6": { - "description": "could bind a socket to ::1", + "healthy": { + "description": "Healthy is deprecated and left for backward compatibility purposes, use `Severity` instead.", "type": "boolean" }, - "pcp": { - "description": "PCP is whether PCP appears present on the LAN.\nEmpty means not checked.", - "type": "string" + "netcheck": { + "$ref": "#/definitions/netcheck.Report" }, - "pmp": { - "description": "PMP is whether NAT-PMP appears present on the LAN.\nEmpty means not checked.", + "netcheck_err": { "type": "string" }, - "preferredDERP": { - "description": "or 0 for unknown", - "type": "integer" - }, - "regionLatency": { - "description": "keyed by DERP Region ID", - "type": "object", - "additionalProperties": { - "type": "integer" + "netcheck_logs": { + "type": "array", + "items": { + "type": "string" } }, - "regionV4Latency": { - "description": "keyed by DERP Region ID", + "regions": { "type": "object", "additionalProperties": { - "type": "integer" + "$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" + }, + "netcheck.Report": { + "type": "object", + "properties": { + "captivePortal": { + "description": "CaptivePortal is set when we think there's a captive portal that is\nintercepting HTTP traffic.", + "type": "string" + }, + "globalV4": { + "description": "ip:port of global IPv4", + "type": "string" + }, + "globalV6": { + "description": "[ip]:port of global IPv6", + "type": "string" + }, + "hairPinning": { + "description": "HairPinning is whether the router supports communicating\nbetween two local devices through the NATted public IP address\n(on IPv4).", + "type": "string" + }, + "icmpv4": { + "description": "an ICMPv4 round trip completed", + "type": "boolean" + }, + "ipv4": { + "description": "an IPv4 STUN round trip completed", + "type": "boolean" + }, + "ipv4CanSend": { + "description": "an IPv4 packet was able to be sent", + "type": "boolean" + }, + "ipv6": { + "description": "an IPv6 STUN round trip completed", + "type": "boolean" + }, + "ipv6CanSend": { + "description": "an IPv6 packet was able to be sent", + "type": "boolean" + }, + "mappingVariesByDestIP": { + "description": "MappingVariesByDestIP is whether STUN results depend which\nSTUN server you're talking to (on IPv4).", + "type": "string" + }, + "oshasIPv6": { + "description": "could bind a socket to ::1", + "type": "boolean" + }, + "pcp": { + "description": "PCP is whether PCP appears present on the LAN.\nEmpty means not checked.", + "type": "string" + }, + "pmp": { + "description": "PMP is whether NAT-PMP appears present on the LAN.\nEmpty means not checked.", + "type": "string" + }, + "preferredDERP": { + "description": "or 0 for unknown", + "type": "integer" + }, + "regionLatency": { + "description": "keyed by DERP Region ID", + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "regionV4Latency": { + "description": "keyed by DERP Region ID", + "type": "object", + "additionalProperties": { + "type": "integer" } }, "regionV6Latency": { @@ -13216,6 +13202,20 @@ } } }, + "workspacesdk.WorkspaceAgentConnectionInfo": { + "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/debug.go b/coderd/debug.go index 7cef04640d6c2..d97edfe0b73fe 100644 --- a/coderd/debug.go +++ b/coderd/debug.go @@ -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() diff --git a/docs/api/agents.md b/docs/api/agents.md index 75ec45d751880..938180f805cca 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.WorkspaceAgentConnectionInfo](schemas.md#workspacesdkworkspaceagentconnectioninfo) | 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 6f9bee2f553d9..917088de576ba 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 710f7d93fb95b..64aa47fcc29e0 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,427 +1755,23 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `path` | string | false | | | | `url` | string | false | | | -## codersdk.DERPHealthReport +## codersdk.DERPRegion ```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" - } - ] + "latency_ms": 0, + "preferred": true } ``` ### 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 | | | +| Name | Type | Required | Restrictions | Description | +| ------------ | ------- | -------- | ------------ | ----------- | +| `latency_ms` | number | false | | | +| `preferred` | boolean | 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 -{ - "latency_ms": 0, - "preferred": true -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | ------- | -------- | ------------ | ----------- | -| `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 +## 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,1703 +3016,1921 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | `refresh` | integer | false | | | | `threshold_database` | integer | false | | | -## codersdk.HealthcheckReport +## codersdk.InsightsReportInterval ```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" - } - ] - } +"day" +``` + +### Properties + +#### Enumerated Values + +| Value | +| ------ | +| `day` | +| `week` | + +## codersdk.IssueReconnectingPTYSignedTokenRequest + +```json +{ + "agentID": "bc282582-04f9-45ce-b904-3e3bfab66958", + "url": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| --------- | ------ | -------- | ------------ | ---------------------------------------------------------------------- | +| `agentID` | string | true | | | +| `url` | string | true | | URL is the URL of the reconnecting-pty endpoint you are connecting to. | + +## codersdk.IssueReconnectingPTYSignedTokenResponse + +```json +{ + "signed_token": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------- | ------ | -------- | ------------ | ----------- | +| `signed_token` | string | false | | | + +## codersdk.JFrogXrayScan + +```json +{ + "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", + "critical": 0, + "high": 0, + "medium": 0, + "results_url": "string", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------- | ------- | -------- | ------------ | ----------- | +| `agent_id` | string | false | | | +| `critical` | integer | false | | | +| `high` | integer | false | | | +| `medium` | integer | false | | | +| `results_url` | string | false | | | +| `workspace_id` | string | false | | | + +## codersdk.JobErrorCode + +```json +"REQUIRED_TEMPLATE_VARIABLES" +``` + +### Properties + +#### Enumerated Values + +| Value | +| ----------------------------- | +| `REQUIRED_TEMPLATE_VARIABLES` | + +## codersdk.License + +```json +{ + "claims": {}, + "id": 0, + "uploaded_at": "2019-08-24T14:15:22Z", + "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------- | ------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `claims` | object | false | | Claims are the JWT claims asserted by the license. Here we use a generic string map to ensure that all data from the server is parsed verbatim, not just the fields this version of Coder understands. | +| `id` | integer | false | | | +| `uploaded_at` | string | false | | | +| `uuid` | string | false | | | + +## codersdk.LinkConfig + +```json +{ + "icon": "bug", + "name": "string", + "target": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------- | ------ | -------- | ------------ | ----------- | +| `icon` | string | false | | | +| `name` | string | false | | | +| `target` | string | false | | | + +#### Enumerated Values + +| Property | Value | +| -------- | ------ | +| `icon` | `bug` | +| `icon` | `chat` | +| `icon` | `docs` | + +## codersdk.LogLevel + +```json +"trace" +``` + +### Properties + +#### Enumerated Values + +| Value | +| ------- | +| `trace` | +| `debug` | +| `info` | +| `warn` | +| `error` | + +## codersdk.LogSource + +```json +"provisioner_daemon" +``` + +### Properties + +#### Enumerated Values + +| Value | +| -------------------- | +| `provisioner_daemon` | +| `provisioner` | + +## codersdk.LoggingConfig + +```json +{ + "human": "string", + "json": "string", + "log_filter": ["string"], + "stackdriver": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------- | --------------- | -------- | ------------ | ----------- | +| `human` | string | false | | | +| `json` | string | false | | | +| `log_filter` | array of string | false | | | +| `stackdriver` | string | false | | | + +## codersdk.LoginType + +```json +"" +``` + +### Properties + +#### Enumerated Values + +| Value | +| ---------- | +| `` | +| `password` | +| `github` | +| `oidc` | +| `token` | +| `none` | + +## codersdk.LoginWithPasswordRequest + +```json +{ + "email": "user@example.com", + "password": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------- | ------ | -------- | ------------ | ----------- | +| `email` | string | true | | | +| `password` | string | true | | | + +## codersdk.LoginWithPasswordResponse + +```json +{ + "session_token": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| --------------- | ------ | -------- | ------------ | ----------- | +| `session_token` | string | true | | | + +## codersdk.MinimalUser + +```json +{ + "avatar_url": "http://example.com", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "username": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------ | ------ | -------- | ------------ | ----------- | +| `avatar_url` | string | false | | | +| `id` | string | true | | | +| `username` | string | true | | | + +## codersdk.OAuth2AppEndpoints + +```json +{ + "authorization": "string", + "device_authorization": "string", + "token": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------------------- | ------ | -------- | ------------ | --------------------------------- | +| `authorization` | string | false | | | +| `device_authorization` | string | false | | Device authorization is optional. | +| `token` | string | false | | | + +## codersdk.OAuth2Config + +```json +{ + "github": { + "allow_everyone": true, + "allow_signups": true, + "allowed_orgs": ["string"], + "allowed_teams": ["string"], + "client_id": "string", + "client_secret": "string", + "enterprise_base_url": "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 | | | +| Name | Type | Required | Restrictions | Description | +| -------- | ---------------------------------------------------------- | -------- | ------------ | ----------- | +| `github` | [codersdk.OAuth2GithubConfig](#codersdkoauth2githubconfig) | false | | | + +## codersdk.OAuth2GithubConfig -#### Enumerated Values +```json +{ + "allow_everyone": true, + "allow_signups": true, + "allowed_orgs": ["string"], + "allowed_teams": ["string"], + "client_id": "string", + "client_secret": "string", + "enterprise_base_url": "string" +} +``` -| Property | Value | -| ---------- | --------- | -| `severity` | `ok` | -| `severity` | `warning` | -| `severity` | `error` | +### Properties -## codersdk.InsightsReportInterval +| Name | Type | Required | Restrictions | Description | +| --------------------- | --------------- | -------- | ------------ | ----------- | +| `allow_everyone` | boolean | false | | | +| `allow_signups` | boolean | false | | | +| `allowed_orgs` | array of string | false | | | +| `allowed_teams` | array of string | false | | | +| `client_id` | string | false | | | +| `client_secret` | string | false | | | +| `enterprise_base_url` | string | false | | | + +## codersdk.OAuth2ProviderApp ```json -"day" +{ + "callback_url": "string", + "endpoints": { + "authorization": "string", + "device_authorization": "string", + "token": "string" + }, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string" +} ``` ### Properties -#### Enumerated Values +| Name | Type | Required | Restrictions | Description | +| -------------- | ---------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `callback_url` | string | false | | | +| `endpoints` | [codersdk.OAuth2AppEndpoints](#codersdkoauth2appendpoints) | false | | Endpoints are included in the app response for easier discovery. The OAuth2 spec does not have a defined place to find these (for comparison, OIDC has a '/.well-known/openid-configuration' endpoint). | +| `icon` | string | false | | | +| `id` | string | false | | | +| `name` | string | false | | | -| Value | -| ------ | -| `day` | -| `week` | +## codersdk.OAuth2ProviderAppSecret -## codersdk.IssueReconnectingPTYSignedTokenRequest +```json +{ + "client_secret_truncated": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "last_used_at": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------------- | ------ | -------- | ------------ | ----------- | +| `client_secret_truncated` | string | false | | | +| `id` | string | false | | | +| `last_used_at` | string | false | | | + +## codersdk.OAuth2ProviderAppSecretFull + +```json +{ + "client_secret_full": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------ | -------- | ------------ | ----------- | +| `client_secret_full` | string | false | | | +| `id` | string | false | | | + +## codersdk.OAuthConversionResponse + +```json +{ + "expires_at": "2019-08-24T14:15:22Z", + "state_string": "string", + "to_type": "", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------- | ---------------------------------------- | -------- | ------------ | ----------- | +| `expires_at` | string | false | | | +| `state_string` | string | false | | | +| `to_type` | [codersdk.LoginType](#codersdklogintype) | false | | | +| `user_id` | string | false | | | + +## codersdk.OIDCAuthMethod + +```json +{ + "enabled": true, + "iconUrl": "string", + "signInText": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------ | ------- | -------- | ------------ | ----------- | +| `enabled` | boolean | false | | | +| `iconUrl` | string | false | | | +| `signInText` | string | false | | | + +## codersdk.OIDCConfig + +```json +{ + "allow_signups": true, + "auth_url_params": {}, + "client_cert_file": "string", + "client_id": "string", + "client_key_file": "string", + "client_secret": "string", + "email_domain": ["string"], + "email_field": "string", + "group_allow_list": ["string"], + "group_auto_create": true, + "group_mapping": {}, + "group_regex_filter": {}, + "groups_field": "string", + "icon_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "ignore_email_verified": true, + "ignore_user_info": true, + "issuer_url": "string", + "scopes": ["string"], + "sign_in_text": "string", + "signups_disabled_text": "string", + "user_role_field": "string", + "user_role_mapping": {}, + "user_roles_default": ["string"], + "username_field": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ----------------------- | -------------------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | +| `allow_signups` | boolean | false | | | +| `auth_url_params` | object | false | | | +| `client_cert_file` | string | false | | | +| `client_id` | string | false | | | +| `client_key_file` | string | false | | Client key file & ClientCertFile are used in place of ClientSecret for PKI auth. | +| `client_secret` | string | false | | | +| `email_domain` | array of string | false | | | +| `email_field` | string | false | | | +| `group_allow_list` | array of string | false | | | +| `group_auto_create` | boolean | false | | | +| `group_mapping` | object | false | | | +| `group_regex_filter` | [serpent.Regexp](#serpentregexp) | false | | | +| `groups_field` | string | false | | | +| `icon_url` | [serpent.URL](#serpenturl) | false | | | +| `ignore_email_verified` | boolean | false | | | +| `ignore_user_info` | boolean | false | | | +| `issuer_url` | string | false | | | +| `scopes` | array of string | false | | | +| `sign_in_text` | string | false | | | +| `signups_disabled_text` | string | false | | | +| `user_role_field` | string | false | | | +| `user_role_mapping` | object | false | | | +| `user_roles_default` | array of string | false | | | +| `username_field` | string | false | | | + +## codersdk.Organization + +```json +{ + "created_at": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "is_default": true, + "name": "string", + "updated_at": "2019-08-24T14:15:22Z" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------ | ------- | -------- | ------------ | ----------- | +| `created_at` | string | true | | | +| `id` | string | true | | | +| `is_default` | boolean | true | | | +| `name` | string | true | | | +| `updated_at` | string | true | | | + +## codersdk.OrganizationMember ```json { - "agentID": "bc282582-04f9-45ce-b904-3e3bfab66958", - "url": "string" + "created_at": "2019-08-24T14:15:22Z", + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "roles": [ + { + "display_name": "string", + "name": "string" + } + ], + "updated_at": "2019-08-24T14:15:22Z", + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------ | -------- | ------------ | ---------------------------------------------------------------------- | -| `agentID` | string | true | | | -| `url` | string | true | | URL is the URL of the reconnecting-pty endpoint you are connecting to. | +| Name | Type | Required | Restrictions | Description | +| ----------------- | --------------------------------------- | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `organization_id` | string | false | | | +| `roles` | array of [codersdk.Role](#codersdkrole) | false | | | +| `updated_at` | string | false | | | +| `user_id` | string | false | | | -## codersdk.IssueReconnectingPTYSignedTokenResponse +## codersdk.PatchGroupRequest ```json { - "signed_token": "string" + "add_users": ["string"], + "avatar_url": "string", + "display_name": "string", + "name": "string", + "quota_allowance": 0, + "remove_users": ["string"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------ | -------- | ------------ | ----------- | -| `signed_token` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ----------------- | --------------- | -------- | ------------ | ----------- | +| `add_users` | array of string | false | | | +| `avatar_url` | string | false | | | +| `display_name` | string | false | | | +| `name` | string | false | | | +| `quota_allowance` | integer | false | | | +| `remove_users` | array of string | false | | | -## codersdk.JFrogXrayScan +## codersdk.PatchTemplateVersionRequest ```json { - "agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978", - "critical": 0, - "high": 0, - "medium": 0, - "results_url": "string", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" + "message": "string", + "name": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------- | -------- | ------------ | ----------- | -| `agent_id` | string | false | | | -| `critical` | integer | false | | | -| `high` | integer | false | | | -| `medium` | integer | false | | | -| `results_url` | string | false | | | -| `workspace_id` | string | false | | | - -## codersdk.JobErrorCode - -```json -"REQUIRED_TEMPLATE_VARIABLES" -``` - -### Properties - -#### Enumerated Values - -| Value | -| ----------------------------- | -| `REQUIRED_TEMPLATE_VARIABLES` | +| Name | Type | Required | Restrictions | Description | +| --------- | ------ | -------- | ------------ | ----------- | +| `message` | string | false | | | +| `name` | string | false | | | -## codersdk.License +## codersdk.PatchWorkspaceProxy ```json { - "claims": {}, - "id": 0, - "uploaded_at": "2019-08-24T14:15:22Z", - "uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f" + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "regenerate_token": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | ------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `claims` | object | false | | Claims are the JWT claims asserted by the license. Here we use a generic string map to ensure that all data from the server is parsed verbatim, not just the fields this version of Coder understands. | -| `id` | integer | false | | | -| `uploaded_at` | string | false | | | -| `uuid` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------- | -------- | ------------ | ----------- | +| `display_name` | string | true | | | +| `icon` | string | true | | | +| `id` | string | true | | | +| `name` | string | true | | | +| `regenerate_token` | boolean | false | | | -## codersdk.LinkConfig +## codersdk.PostOAuth2ProviderAppRequest ```json { - "icon": "bug", - "name": "string", - "target": "string" + "callback_url": "string", + "icon": "string", + "name": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | ------ | -------- | ------------ | ----------- | -| `icon` | string | false | | | -| `name` | string | false | | | -| `target` | string | false | | | - -#### Enumerated Values - -| Property | Value | -| -------- | ------ | -| `icon` | `bug` | -| `icon` | `chat` | -| `icon` | `docs` | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------ | -------- | ------------ | ----------- | +| `callback_url` | string | true | | | +| `icon` | string | false | | | +| `name` | string | true | | | -## codersdk.LogLevel +## codersdk.PprofConfig ```json -"trace" +{ + "address": { + "host": "string", + "port": "string" + }, + "enable": true +} ``` ### Properties -#### Enumerated Values - -| Value | -| ------- | -| `trace` | -| `debug` | -| `info` | -| `warn` | -| `error` | +| Name | Type | Required | Restrictions | Description | +| --------- | ------------------------------------ | -------- | ------------ | ----------- | +| `address` | [serpent.HostPort](#serpenthostport) | false | | | +| `enable` | boolean | false | | | -## codersdk.LogSource +## codersdk.PrometheusConfig ```json -"provisioner_daemon" +{ + "address": { + "host": "string", + "port": "string" + }, + "aggregate_agent_stats_by": ["string"], + "collect_agent_stats": true, + "collect_db_metrics": true, + "enable": true +} ``` ### Properties -#### Enumerated Values - -| Value | -| -------------------- | -| `provisioner_daemon` | -| `provisioner` | +| Name | Type | Required | Restrictions | Description | +| -------------------------- | ------------------------------------ | -------- | ------------ | ----------- | +| `address` | [serpent.HostPort](#serpenthostport) | false | | | +| `aggregate_agent_stats_by` | array of string | false | | | +| `collect_agent_stats` | boolean | false | | | +| `collect_db_metrics` | boolean | false | | | +| `enable` | boolean | false | | | -## codersdk.LoggingConfig +## codersdk.ProvisionerConfig ```json { - "human": "string", - "json": "string", - "log_filter": ["string"], - "stackdriver": "string" + "daemon_poll_interval": 0, + "daemon_poll_jitter": 0, + "daemon_psk": "string", + "daemons": 0, + "daemons_echo": true, + "force_cancel_interval": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | --------------- | -------- | ------------ | ----------- | -| `human` | string | false | | | -| `json` | string | false | | | -| `log_filter` | array of string | false | | | -| `stackdriver` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ----------------------- | ------- | -------- | ------------ | ----------- | +| `daemon_poll_interval` | integer | false | | | +| `daemon_poll_jitter` | integer | false | | | +| `daemon_psk` | string | false | | | +| `daemons` | integer | false | | | +| `daemons_echo` | boolean | false | | | +| `force_cancel_interval` | integer | false | | | -## codersdk.LoginType +## codersdk.ProvisionerDaemon ```json -"" +{ + "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" +} ``` ### Properties -#### Enumerated Values - -| Value | -| ---------- | -| `` | -| `password` | -| `github` | -| `oidc` | -| `token` | -| `none` | +| Name | Type | Required | Restrictions | Description | +| ------------------ | --------------- | -------- | ------------ | ----------- | +| `api_version` | string | false | | | +| `created_at` | string | false | | | +| `id` | string | false | | | +| `last_seen_at` | string | false | | | +| `name` | string | false | | | +| `provisioners` | array of string | false | | | +| `tags` | object | false | | | +| » `[any property]` | string | false | | | +| `version` | string | false | | | -## codersdk.LoginWithPasswordRequest +## codersdk.ProvisionerJob ```json { - "email": "user@example.com", - "password": "string" + "canceled_at": "2019-08-24T14:15:22Z", + "completed_at": "2019-08-24T14:15:22Z", + "created_at": "2019-08-24T14:15:22Z", + "error": "string", + "error_code": "REQUIRED_TEMPLATE_VARIABLES", + "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, + "started_at": "2019-08-24T14:15:22Z", + "status": "pending", + "tags": { + "property1": "string", + "property2": "string" + }, + "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------- | ------ | -------- | ------------ | ----------- | -| `email` | string | true | | | -| `password` | string | true | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | -------------------------------------------------------------- | -------- | ------------ | ----------- | +| `canceled_at` | string | false | | | +| `completed_at` | string | false | | | +| `created_at` | string | false | | | +| `error` | string | false | | | +| `error_code` | [codersdk.JobErrorCode](#codersdkjoberrorcode) | false | | | +| `file_id` | string | false | | | +| `id` | string | false | | | +| `queue_position` | integer | false | | | +| `queue_size` | integer | false | | | +| `started_at` | string | false | | | +| `status` | [codersdk.ProvisionerJobStatus](#codersdkprovisionerjobstatus) | false | | | +| `tags` | object | false | | | +| » `[any property]` | string | false | | | +| `worker_id` | string | false | | | -## codersdk.LoginWithPasswordResponse +#### Enumerated Values + +| Property | Value | +| ------------ | ----------------------------- | +| `error_code` | `REQUIRED_TEMPLATE_VARIABLES` | +| `status` | `pending` | +| `status` | `running` | +| `status` | `succeeded` | +| `status` | `canceling` | +| `status` | `canceled` | +| `status` | `failed` | + +## codersdk.ProvisionerJobLog ```json { - "session_token": "string" + "created_at": "2019-08-24T14:15:22Z", + "id": 0, + "log_level": "trace", + "log_source": "provisioner_daemon", + "output": "string", + "stage": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------------- | ------ | -------- | ------------ | ----------- | -| `session_token` | string | true | | | +| Name | Type | Required | Restrictions | Description | +| ------------ | ---------------------------------------- | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `id` | integer | false | | | +| `log_level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | +| `log_source` | [codersdk.LogSource](#codersdklogsource) | false | | | +| `output` | string | false | | | +| `stage` | string | false | | | -## codersdk.MinimalUser +#### Enumerated Values + +| Property | Value | +| ----------- | ------- | +| `log_level` | `trace` | +| `log_level` | `debug` | +| `log_level` | `info` | +| `log_level` | `warn` | +| `log_level` | `error` | + +## codersdk.ProvisionerJobStatus ```json -{ - "avatar_url": "http://example.com", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "username": "string" -} +"pending" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------ | ------ | -------- | ------------ | ----------- | -| `avatar_url` | string | false | | | -| `id` | string | true | | | -| `username` | string | true | | | +#### Enumerated Values -## codersdk.OAuth2AppEndpoints +| Value | +| ----------- | +| `pending` | +| `running` | +| `succeeded` | +| `canceling` | +| `canceled` | +| `failed` | +| `unknown` | + +## codersdk.ProvisionerLogLevel ```json -{ - "authorization": "string", - "device_authorization": "string", - "token": "string" -} +"debug" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------------- | ------ | -------- | ------------ | --------------------------------- | -| `authorization` | string | false | | | -| `device_authorization` | string | false | | Device authorization is optional. | -| `token` | string | false | | | +#### Enumerated Values -## codersdk.OAuth2Config +| Value | +| ------- | +| `debug` | + +## codersdk.ProvisionerStorageMethod ```json -{ - "github": { - "allow_everyone": true, - "allow_signups": true, - "allowed_orgs": ["string"], - "allowed_teams": ["string"], - "client_id": "string", - "client_secret": "string", - "enterprise_base_url": "string" - } -} +"file" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | ---------------------------------------------------------- | -------- | ------------ | ----------- | -| `github` | [codersdk.OAuth2GithubConfig](#codersdkoauth2githubconfig) | false | | | +#### Enumerated Values -## codersdk.OAuth2GithubConfig +| Value | +| ------ | +| `file` | + +## codersdk.ProxyHealthReport ```json { - "allow_everyone": true, - "allow_signups": true, - "allowed_orgs": ["string"], - "allowed_teams": ["string"], - "client_id": "string", - "client_secret": "string", - "enterprise_base_url": "string" + "errors": ["string"], + "warnings": ["string"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------------------- | --------------- | -------- | ------------ | ----------- | -| `allow_everyone` | boolean | false | | | -| `allow_signups` | boolean | false | | | -| `allowed_orgs` | array of string | false | | | -| `allowed_teams` | array of string | false | | | -| `client_id` | string | false | | | -| `client_secret` | string | false | | | -| `enterprise_base_url` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------- | --------------- | -------- | ------------ | ---------------------------------------------------------------------------------------- | +| `errors` | array of string | false | | Errors are problems that prevent the workspace proxy from being healthy | +| `warnings` | array of string | false | | Warnings do not prevent the workspace proxy from being healthy, but should be addressed. | -## codersdk.OAuth2ProviderApp +## codersdk.ProxyHealthStatus ```json -{ - "callback_url": "string", - "endpoints": { - "authorization": "string", - "device_authorization": "string", - "token": "string" - }, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "name": "string" -} +"ok" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ---------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `callback_url` | string | false | | | -| `endpoints` | [codersdk.OAuth2AppEndpoints](#codersdkoauth2appendpoints) | false | | Endpoints are included in the app response for easier discovery. The OAuth2 spec does not have a defined place to find these (for comparison, OIDC has a '/.well-known/openid-configuration' endpoint). | -| `icon` | string | false | | | -| `id` | string | false | | | -| `name` | string | false | | | +#### Enumerated Values -## codersdk.OAuth2ProviderAppSecret +| Value | +| -------------- | +| `ok` | +| `unreachable` | +| `unhealthy` | +| `unregistered` | + +## codersdk.PutExtendWorkspaceRequest ```json { - "client_secret_truncated": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "last_used_at": "string" + "deadline": "2019-08-24T14:15:22Z" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------------- | ------ | -------- | ------------ | ----------- | -| `client_secret_truncated` | string | false | | | -| `id` | string | false | | | -| `last_used_at` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------- | ------ | -------- | ------------ | ----------- | +| `deadline` | string | true | | | -## codersdk.OAuth2ProviderAppSecretFull +## codersdk.PutOAuth2ProviderAppRequest ```json { - "client_secret_full": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08" + "callback_url": "string", + "icon": "string", + "name": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------------- | ------ | -------- | ------------ | ----------- | -| `client_secret_full` | string | false | | | -| `id` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------ | -------- | ------------ | ----------- | +| `callback_url` | string | true | | | +| `icon` | string | false | | | +| `name` | string | true | | | -## codersdk.OAuthConversionResponse +## codersdk.RBACResource ```json -{ - "expires_at": "2019-08-24T14:15:22Z", - "state_string": "string", - "to_type": "", - "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" -} +"workspace" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ---------------------------------------- | -------- | ------------ | ----------- | -| `expires_at` | string | false | | | -| `state_string` | string | false | | | -| `to_type` | [codersdk.LoginType](#codersdklogintype) | false | | | -| `user_id` | string | false | | | +#### Enumerated Values + +| Value | +| --------------------------------- | +| `workspace` | +| `workspace_proxy` | +| `workspace_execution` | +| `application_connect` | +| `audit_log` | +| `template` | +| `group` | +| `file` | +| `provisioner_daemon` | +| `organization` | +| `assign_role` | +| `assign_org_role` | +| `api_key` | +| `user` | +| `user_data` | +| `user_workspace_build_parameters` | +| `organization_member` | +| `license` | +| `deployment_config` | +| `deployment_stats` | +| `replicas` | +| `debug_info` | +| `system` | +| `template_insights` | -## codersdk.OIDCAuthMethod +## codersdk.RateLimitConfig ```json { - "enabled": true, - "iconUrl": "string", - "signInText": "string" + "api": 0, + "disable_all": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------ | ------- | -------- | ------------ | ----------- | -| `enabled` | boolean | false | | | -| `iconUrl` | string | false | | | -| `signInText` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------- | ------- | -------- | ------------ | ----------- | +| `api` | integer | false | | | +| `disable_all` | boolean | false | | | -## codersdk.OIDCConfig +## codersdk.ReducedUser ```json { - "allow_signups": true, - "auth_url_params": {}, - "client_cert_file": "string", - "client_id": "string", - "client_key_file": "string", - "client_secret": "string", - "email_domain": ["string"], - "email_field": "string", - "group_allow_list": ["string"], - "group_auto_create": true, - "group_mapping": {}, - "group_regex_filter": {}, - "groups_field": "string", - "icon_url": { - "forceQuery": true, - "fragment": "string", - "host": "string", - "omitHost": true, - "opaque": "string", - "path": "string", - "rawFragment": "string", - "rawPath": "string", - "rawQuery": "string", - "scheme": "string", - "user": {} - }, - "ignore_email_verified": true, - "ignore_user_info": true, - "issuer_url": "string", - "scopes": ["string"], - "sign_in_text": "string", - "signups_disabled_text": "string", - "user_role_field": "string", - "user_role_mapping": {}, - "user_roles_default": ["string"], - "username_field": "string" + "avatar_url": "http://example.com", + "created_at": "2019-08-24T14:15:22Z", + "email": "user@example.com", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "last_seen_at": "2019-08-24T14:15:22Z", + "login_type": "", + "name": "string", + "status": "active", + "theme_preference": "string", + "username": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ----------------------- | -------------------------------- | -------- | ------------ | -------------------------------------------------------------------------------- | -| `allow_signups` | boolean | false | | | -| `auth_url_params` | object | false | | | -| `client_cert_file` | string | false | | | -| `client_id` | string | false | | | -| `client_key_file` | string | false | | Client key file & ClientCertFile are used in place of ClientSecret for PKI auth. | -| `client_secret` | string | false | | | -| `email_domain` | array of string | false | | | -| `email_field` | string | false | | | -| `group_allow_list` | array of string | false | | | -| `group_auto_create` | boolean | false | | | -| `group_mapping` | object | false | | | -| `group_regex_filter` | [serpent.Regexp](#serpentregexp) | false | | | -| `groups_field` | string | false | | | -| `icon_url` | [serpent.URL](#serpenturl) | false | | | -| `ignore_email_verified` | boolean | false | | | -| `ignore_user_info` | boolean | false | | | -| `issuer_url` | string | false | | | -| `scopes` | array of string | false | | | -| `sign_in_text` | string | false | | | -| `signups_disabled_text` | string | false | | | -| `user_role_field` | string | false | | | -| `user_role_mapping` | object | false | | | -| `user_roles_default` | array of string | false | | | -| `username_field` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------------------------------------------ | -------- | ------------ | ----------- | +| `avatar_url` | string | false | | | +| `created_at` | string | true | | | +| `email` | string | true | | | +| `id` | string | true | | | +| `last_seen_at` | string | false | | | +| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | | +| `name` | string | false | | | +| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | | +| `theme_preference` | string | false | | | +| `username` | string | true | | | -## codersdk.Organization +#### Enumerated Values + +| Property | Value | +| -------- | ----------- | +| `status` | `active` | +| `status` | `suspended` | + +## codersdk.Region ```json { - "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "healthy": true, + "icon_url": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "is_default": true, "name": "string", - "updated_at": "2019-08-24T14:15:22Z" + "path_app_url": "string", + "wildcard_hostname": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------ | ------- | -------- | ------------ | ----------- | -| `created_at` | string | true | | | -| `id` | string | true | | | -| `is_default` | boolean | true | | | -| `name` | string | true | | | -| `updated_at` | string | true | | | +| Name | Type | Required | Restrictions | Description | +| ------------------- | ------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `display_name` | string | false | | | +| `healthy` | boolean | false | | | +| `icon_url` | string | false | | | +| `id` | string | false | | | +| `name` | string | false | | | +| `path_app_url` | string | false | | Path app URL is the URL to the base path for path apps. Optional unless wildcard_hostname is set. E.g. https://us.example.com | +| `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.OrganizationMember +## codersdk.RegionsResponse-codersdk_Region ```json { - "created_at": "2019-08-24T14:15:22Z", - "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", - "roles": [ + "regions": [ { "display_name": "string", - "name": "string" + "healthy": true, + "icon_url": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "name": "string", + "path_app_url": "string", + "wildcard_hostname": "string" } - ], - "updated_at": "2019-08-24T14:15:22Z", - "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ----------------- | --------------------------------------- | -------- | ------------ | ----------- | -| `created_at` | string | false | | | -| `organization_id` | string | false | | | -| `roles` | array of [codersdk.Role](#codersdkrole) | false | | | -| `updated_at` | string | false | | | -| `user_id` | string | false | | | - -## codersdk.PatchGroupRequest - -```json -{ - "add_users": ["string"], - "avatar_url": "string", - "display_name": "string", - "name": "string", - "quota_allowance": 0, - "remove_users": ["string"] + ] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ----------------- | --------------- | -------- | ------------ | ----------- | -| `add_users` | array of string | false | | | -| `avatar_url` | string | false | | | -| `display_name` | string | false | | | -| `name` | string | false | | | -| `quota_allowance` | integer | false | | | -| `remove_users` | array of string | false | | | +| Name | Type | Required | Restrictions | Description | +| --------- | ------------------------------------------- | -------- | ------------ | ----------- | +| `regions` | array of [codersdk.Region](#codersdkregion) | false | | | -## codersdk.PatchTemplateVersionRequest +## codersdk.RegionsResponse-codersdk_WorkspaceProxy ```json { - "message": "string", - "name": "string" + "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 | -| --------- | ------ | -------- | ------------ | ----------- | -| `message` | string | false | | | -| `name` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| --------- | ----------------------------------------------------------- | -------- | ------------ | ----------- | +| `regions` | array of [codersdk.WorkspaceProxy](#codersdkworkspaceproxy) | false | | | -## codersdk.PatchWorkspaceProxy +## codersdk.Replica ```json { - "display_name": "string", - "icon": "string", + "created_at": "2019-08-24T14:15:22Z", + "database_latency": 0, + "error": "string", + "hostname": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "name": "string", - "regenerate_token": true + "region_id": 0, + "relay_address": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------- | -------- | ------------ | ----------- | -| `display_name` | string | true | | | -| `icon` | string | true | | | -| `id` | string | true | | | -| `name` | string | true | | | -| `regenerate_token` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------- | -------- | ------------ | ------------------------------------------------------------------ | +| `created_at` | string | false | | Created at is the timestamp when the replica was first seen. | +| `database_latency` | integer | false | | Database latency is the latency in microseconds to the database. | +| `error` | string | false | | Error is the replica error. | +| `hostname` | string | false | | Hostname is the hostname of the replica. | +| `id` | string | false | | ID is the unique identifier for the replica. | +| `region_id` | integer | false | | Region ID is the region of the replica. | +| `relay_address` | string | false | | Relay address is the accessible address to relay DERP connections. | -## codersdk.PostOAuth2ProviderAppRequest +## codersdk.ResolveAutostartResponse ```json { - "callback_url": "string", - "icon": "string", - "name": "string" + "parameter_mismatch": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------ | -------- | ------------ | ----------- | -| `callback_url` | string | true | | | -| `icon` | string | false | | | -| `name` | string | true | | | +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------- | -------- | ------------ | ----------- | +| `parameter_mismatch` | boolean | false | | | -## codersdk.PprofConfig +## codersdk.ResourceType ```json -{ - "address": { - "host": "string", - "port": "string" - }, - "enable": true -} +"template" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------------------------------------ | -------- | ------------ | ----------- | -| `address` | [serpent.HostPort](#serpenthostport) | false | | | -| `enable` | boolean | false | | | +#### Enumerated Values -## codersdk.PrometheusConfig +| Value | +| ---------------------------- | +| `template` | +| `template_version` | +| `user` | +| `workspace` | +| `workspace_build` | +| `git_ssh_key` | +| `api_key` | +| `group` | +| `license` | +| `convert_login` | +| `health_settings` | +| `workspace_proxy` | +| `organization` | +| `oauth2_provider_app` | +| `oauth2_provider_app_secret` | + +## codersdk.Response ```json { - "address": { - "host": "string", - "port": "string" - }, - "aggregate_agent_stats_by": ["string"], - "collect_agent_stats": true, - "collect_db_metrics": true, - "enable": true + "detail": "string", + "message": "string", + "validations": [ + { + "detail": "string", + "field": "string" + } + ] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------------------- | ------------------------------------ | -------- | ------------ | ----------- | -| `address` | [serpent.HostPort](#serpenthostport) | false | | | -| `aggregate_agent_stats_by` | array of string | false | | | -| `collect_agent_stats` | boolean | false | | | -| `collect_db_metrics` | boolean | false | | | -| `enable` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------- | ------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `detail` | string | false | | Detail is a debug message that provides further insight into why the action failed. This information can be technical and a regular golang err.Error() text. - "database: too many open connections" - "stat: too many open files" | +| `message` | string | false | | Message is an actionable message that depicts actions the request took. These messages should be fully formed sentences with proper punctuation. Examples: - "A user has been created." - "Failed to create a user." | +| `validations` | array of [codersdk.ValidationError](#codersdkvalidationerror) | false | | Validations are form field-specific friendly error messages. They will be shown on a form field in the UI. These can also be used to add additional context if there is a set of errors in the primary 'Message'. | -## codersdk.ProvisionerConfig +## codersdk.Role ```json { - "daemon_poll_interval": 0, - "daemon_poll_jitter": 0, - "daemon_psk": "string", - "daemons": 0, - "daemons_echo": true, - "force_cancel_interval": 0 + "display_name": "string", + "name": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ----------------------- | ------- | -------- | ------------ | ----------- | -| `daemon_poll_interval` | integer | false | | | -| `daemon_poll_jitter` | integer | false | | | -| `daemon_psk` | string | false | | | -| `daemons` | integer | false | | | -| `daemons_echo` | boolean | false | | | -| `force_cancel_interval` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------ | -------- | ------------ | ----------- | +| `display_name` | string | false | | | +| `name` | string | false | | | -## codersdk.ProvisionerDaemon +## codersdk.SSHConfig ```json { - "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" + "deploymentName": "string", + "sshconfigOptions": ["string"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | --------------- | -------- | ------------ | ----------- | -| `api_version` | string | false | | | -| `created_at` | string | false | | | -| `id` | string | false | | | -| `last_seen_at` | string | false | | | -| `name` | string | false | | | -| `provisioners` | array of string | false | | | -| `tags` | object | false | | | -| » `[any property]` | string | false | | | -| `version` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | --------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------- | +| `deploymentName` | string | false | | Deploymentname is the config-ssh Hostname prefix | +| `sshconfigOptions` | array of string | false | | Sshconfigoptions are additional options to add to the ssh config file. This will override defaults. | -## codersdk.ProvisionerDaemonsReport +## codersdk.SSHConfigResponse ```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" - } - ] + "hostname_prefix": "string", + "ssh_config_options": { + "property1": "string", + "property2": "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 | | | +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------ | -------- | ------------ | ----------- | +| `hostname_prefix` | string | false | | | +| `ssh_config_options` | object | false | | | +| » `[any property]` | string | false | | | -## codersdk.ProvisionerDaemonsReportItem +## codersdk.ServiceBannerConfig ```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" - } - ] + "background_color": "string", + "enabled": true, + "message": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------------- | -------------------------------------------------------- | -------- | ------------ | ----------- | -| `provisioner_daemon` | [codersdk.ProvisionerDaemon](#codersdkprovisionerdaemon) | false | | | -| `warnings` | array of [health.Message](#healthmessage) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------- | -------- | ------------ | ----------- | +| `background_color` | string | false | | | +| `enabled` | boolean | false | | | +| `message` | string | false | | | -## codersdk.ProvisionerJob +## codersdk.SessionCountDeploymentStats ```json { - "canceled_at": "2019-08-24T14:15:22Z", - "completed_at": "2019-08-24T14:15:22Z", - "created_at": "2019-08-24T14:15:22Z", - "error": "string", - "error_code": "REQUIRED_TEMPLATE_VARIABLES", - "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "queue_position": 0, - "queue_size": 0, - "started_at": "2019-08-24T14:15:22Z", - "status": "pending", - "tags": { - "property1": "string", - "property2": "string" - }, - "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" + "jetbrains": 0, + "reconnecting_pty": 0, + "ssh": 0, + "vscode": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | -------------------------------------------------------------- | -------- | ------------ | ----------- | -| `canceled_at` | string | false | | | -| `completed_at` | string | false | | | -| `created_at` | string | false | | | -| `error` | string | false | | | -| `error_code` | [codersdk.JobErrorCode](#codersdkjoberrorcode) | false | | | -| `file_id` | string | false | | | -| `id` | string | false | | | -| `queue_position` | integer | false | | | -| `queue_size` | integer | false | | | -| `started_at` | string | false | | | -| `status` | [codersdk.ProvisionerJobStatus](#codersdkprovisionerjobstatus) | false | | | -| `tags` | object | false | | | -| » `[any property]` | string | false | | | -| `worker_id` | string | false | | | - -#### Enumerated Values - -| Property | Value | -| ------------ | ----------------------------- | -| `error_code` | `REQUIRED_TEMPLATE_VARIABLES` | -| `status` | `pending` | -| `status` | `running` | -| `status` | `succeeded` | -| `status` | `canceling` | -| `status` | `canceled` | -| `status` | `failed` | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------- | -------- | ------------ | ----------- | +| `jetbrains` | integer | false | | | +| `reconnecting_pty` | integer | false | | | +| `ssh` | integer | false | | | +| `vscode` | integer | false | | | -## codersdk.ProvisionerJobLog +## codersdk.SupportConfig ```json { - "created_at": "2019-08-24T14:15:22Z", - "id": 0, - "log_level": "trace", - "log_source": "provisioner_daemon", - "output": "string", - "stage": "string" + "links": { + "value": [ + { + "icon": "bug", + "name": "string", + "target": "string" + } + ] + } } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------ | ---------------------------------------- | -------- | ------------ | ----------- | -| `created_at` | string | false | | | -| `id` | integer | false | | | -| `log_level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | -| `log_source` | [codersdk.LogSource](#codersdklogsource) | false | | | -| `output` | string | false | | | -| `stage` | string | false | | | - -#### Enumerated Values - -| Property | Value | -| ----------- | ------- | -| `log_level` | `trace` | -| `log_level` | `debug` | -| `log_level` | `info` | -| `log_level` | `warn` | -| `log_level` | `error` | +| Name | Type | Required | Restrictions | Description | +| ------- | ------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | +| `links` | [serpent.Struct-array_codersdk_LinkConfig](#serpentstruct-array_codersdk_linkconfig) | false | | | -## codersdk.ProvisionerJobStatus +## codersdk.SwaggerConfig ```json -"pending" +{ + "enable": true +} ``` ### Properties -#### Enumerated Values - -| Value | -| ----------- | -| `pending` | -| `running` | -| `succeeded` | -| `canceling` | -| `canceled` | -| `failed` | -| `unknown` | +| Name | Type | Required | Restrictions | Description | +| -------- | ------- | -------- | ------------ | ----------- | +| `enable` | boolean | false | | | -## codersdk.ProvisionerLogLevel +## codersdk.TLSConfig ```json -"debug" +{ + "address": { + "host": "string", + "port": "string" + }, + "allow_insecure_ciphers": true, + "cert_file": ["string"], + "client_auth": "string", + "client_ca_file": "string", + "client_cert_file": "string", + "client_key_file": "string", + "enable": true, + "key_file": ["string"], + "min_version": "string", + "redirect_http": true, + "supported_ciphers": ["string"] +} ``` ### Properties -#### Enumerated Values - -| Value | -| ------- | -| `debug` | +| Name | Type | Required | Restrictions | Description | +| ------------------------ | ------------------------------------ | -------- | ------------ | ----------- | +| `address` | [serpent.HostPort](#serpenthostport) | false | | | +| `allow_insecure_ciphers` | boolean | false | | | +| `cert_file` | array of string | false | | | +| `client_auth` | string | false | | | +| `client_ca_file` | string | false | | | +| `client_cert_file` | string | false | | | +| `client_key_file` | string | false | | | +| `enable` | boolean | false | | | +| `key_file` | array of string | false | | | +| `min_version` | string | false | | | +| `redirect_http` | boolean | false | | | +| `supported_ciphers` | array of string | false | | | -## codersdk.ProvisionerStorageMethod +## codersdk.TelemetryConfig ```json -"file" +{ + "enable": true, + "trace": true, + "url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + } +} ``` ### Properties -#### Enumerated Values - -| Value | -| ------ | -| `file` | +| Name | Type | Required | Restrictions | Description | +| -------- | -------------------------- | -------- | ------------ | ----------- | +| `enable` | boolean | false | | | +| `trace` | boolean | false | | | +| `url` | [serpent.URL](#serpenturl) | false | | | -## codersdk.ProxyHealthReport +## codersdk.Template ```json { - "errors": ["string"], - "warnings": ["string"] + "active_user_count": 0, + "active_version_id": "eae64611-bd53-4a80-bb77-df1e432c0fbc", + "activity_bump_ms": 0, + "allow_user_autostart": true, + "allow_user_autostop": true, + "allow_user_cancel_workspace_jobs": true, + "autostart_requirement": { + "days_of_week": ["monday"] + }, + "autostop_requirement": { + "days_of_week": ["monday"], + "weeks": 0 + }, + "build_time_stats": { + "property1": { + "p50": 123, + "p95": 146 + }, + "property2": { + "p50": 123, + "p95": 146 + } + }, + "created_at": "2019-08-24T14:15:22Z", + "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", + "created_by_name": "string", + "default_ttl_ms": 0, + "deprecated": true, + "deprecation_message": "string", + "description": "string", + "display_name": "string", + "failure_ttl_ms": 0, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "max_port_share_level": "owner", + "name": "string", + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "provisioner": "terraform", + "require_active_version": true, + "time_til_dormant_autodelete_ms": 0, + "time_til_dormant_ms": 0, + "updated_at": "2019-08-24T14:15:22Z" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------- | --------------- | -------- | ------------ | ---------------------------------------------------------------------------------------- | -| `errors` | array of string | false | | Errors are problems that prevent the workspace proxy from being healthy | -| `warnings` | array of string | false | | Warnings do not prevent the workspace proxy from being healthy, but should be addressed. | +| Name | Type | Required | Restrictions | Description | +| ---------------------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `active_user_count` | integer | false | | Active user count is set to -1 when loading. | +| `active_version_id` | string | false | | | +| `activity_bump_ms` | integer | false | | | +| `allow_user_autostart` | boolean | false | | Allow user autostart and AllowUserAutostop are enterprise-only. Their values are only used if your license is entitled to use the advanced template scheduling feature. | +| `allow_user_autostop` | boolean | false | | | +| `allow_user_cancel_workspace_jobs` | boolean | false | | | +| `autostart_requirement` | [codersdk.TemplateAutostartRequirement](#codersdktemplateautostartrequirement) | false | | | +| `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement are enterprise features. Its value is only used if your license is entitled to use the advanced template scheduling feature. | +| `build_time_stats` | [codersdk.TemplateBuildTimeStats](#codersdktemplatebuildtimestats) | false | | | +| `created_at` | string | false | | | +| `created_by_id` | string | false | | | +| `created_by_name` | string | false | | | +| `default_ttl_ms` | integer | false | | | +| `deprecated` | boolean | false | | | +| `deprecation_message` | string | false | | | +| `description` | string | false | | | +| `display_name` | string | false | | | +| `failure_ttl_ms` | integer | false | | Failure ttl ms TimeTilDormantMillis, and TimeTilDormantAutoDeleteMillis are enterprise-only. Their values are used if your license is entitled to use the advanced template scheduling feature. | +| `icon` | string | false | | | +| `id` | string | false | | | +| `max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | | +| `name` | string | false | | | +| `organization_id` | string | false | | | +| `provisioner` | string | false | | | +| `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | +| `time_til_dormant_autodelete_ms` | integer | false | | | +| `time_til_dormant_ms` | integer | false | | | +| `updated_at` | string | false | | | -## codersdk.ProxyHealthStatus +#### Enumerated Values + +| Property | Value | +| ------------- | ----------- | +| `provisioner` | `terraform` | + +## codersdk.TemplateAppUsage ```json -"ok" +{ + "display_name": "Visual Studio Code", + "icon": "string", + "seconds": 80500, + "slug": "vscode", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "type": "builtin" +} ``` ### Properties -#### Enumerated Values - -| Value | -| -------------- | -| `ok` | -| `unreachable` | -| `unhealthy` | -| `unregistered` | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------------------------------------------------------ | -------- | ------------ | ----------- | +| `display_name` | string | false | | | +| `icon` | string | false | | | +| `seconds` | integer | false | | | +| `slug` | string | false | | | +| `template_ids` | array of string | false | | | +| `type` | [codersdk.TemplateAppsType](#codersdktemplateappstype) | false | | | -## codersdk.PutExtendWorkspaceRequest +## codersdk.TemplateAppsType ```json -{ - "deadline": "2019-08-24T14:15:22Z" -} +"builtin" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------- | ------ | -------- | ------------ | ----------- | -| `deadline` | string | true | | | +#### Enumerated Values -## codersdk.PutOAuth2ProviderAppRequest +| Value | +| --------- | +| `builtin` | +| `app` | + +## codersdk.TemplateAutostartRequirement ```json { - "callback_url": "string", - "icon": "string", - "name": "string" + "days_of_week": ["monday"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------ | -------- | ------------ | ----------- | -| `callback_url` | string | true | | | -| `icon` | string | false | | | -| `name` | string | true | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | --------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| `days_of_week` | array of string | false | | Days of week is a list of days of the week in which autostart is allowed to happen. If no days are specified, autostart is not allowed. | -## codersdk.RBACResource +## codersdk.TemplateAutostopRequirement ```json -"workspace" +{ + "days_of_week": ["monday"], + "weeks": 0 +} ``` ### Properties -#### Enumerated Values - -| Value | -| --------------------------------- | -| `workspace` | -| `workspace_proxy` | -| `workspace_execution` | -| `application_connect` | -| `audit_log` | -| `template` | -| `group` | -| `file` | -| `provisioner_daemon` | -| `organization` | -| `assign_role` | -| `assign_org_role` | -| `api_key` | -| `user` | -| `user_data` | -| `user_workspace_build_parameters` | -| `organization_member` | -| `license` | -| `deployment_config` | -| `deployment_stats` | -| `replicas` | -| `debug_info` | -| `system` | -| `template_insights` | +| Name | Type | Required | Restrictions | Description | +| ------------------------------------------------------------------------------------- | --------------- | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `days_of_week` | array of string | false | | Days of week is a list of days of the week on which restarts are required. Restarts happen within the user's quiet hours (in their configured timezone). If no days are specified, restarts are not required. Weekdays cannot be specified twice. | +| Restarts will only happen on weekdays in this list on weeks which line up with Weeks. | +| `weeks` | integer | false | | Weeks is the number of weeks between required restarts. Weeks are synced across all workspaces (and Coder deployments) using modulo math on a hardcoded epoch week of January 2nd, 2023 (the first Monday of 2023). Values of 0 or 1 indicate weekly restarts. Values of 2 indicate fortnightly restarts, etc. | -## codersdk.RateLimitConfig +## codersdk.TemplateBuildTimeStats ```json { - "api": 0, - "disable_all": true + "property1": { + "p50": 123, + "p95": 146 + }, + "property2": { + "p50": 123, + "p95": 146 + } } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | ------- | -------- | ------------ | ----------- | -| `api` | integer | false | | | -| `disable_all` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------------- | ---------------------------------------------------- | -------- | ------------ | ----------- | +| `[any property]` | [codersdk.TransitionStats](#codersdktransitionstats) | false | | | -## codersdk.ReducedUser +## codersdk.TemplateExample ```json { - "avatar_url": "http://example.com", - "created_at": "2019-08-24T14:15:22Z", - "email": "user@example.com", + "description": "string", + "icon": "string", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "", + "markdown": "string", "name": "string", - "status": "active", - "theme_preference": "string", - "username": "string" + "tags": ["string"], + "url": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------------------------------------------ | -------- | ------------ | ----------- | -| `avatar_url` | string | false | | | -| `created_at` | string | true | | | -| `email` | string | true | | | -| `id` | string | true | | | -| `last_seen_at` | string | false | | | -| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | | -| `name` | string | false | | | -| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | | -| `theme_preference` | string | false | | | -| `username` | string | true | | | - -#### Enumerated Values - -| Property | Value | -| -------- | ----------- | -| `status` | `active` | -| `status` | `suspended` | +| Name | Type | Required | Restrictions | Description | +| ------------- | --------------- | -------- | ------------ | ----------- | +| `description` | string | false | | | +| `icon` | string | false | | | +| `id` | string | false | | | +| `markdown` | string | false | | | +| `name` | string | false | | | +| `tags` | array of string | false | | | +| `url` | string | false | | | -## codersdk.Region +## codersdk.TemplateInsightsIntervalReport ```json { - "display_name": "string", - "healthy": true, - "icon_url": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "name": "string", - "path_app_url": "string", - "wildcard_hostname": "string" + "active_users": 14, + "end_time": "2019-08-24T14:15:22Z", + "interval": "week", + "start_time": "2019-08-24T14:15:22Z", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------- | ------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `display_name` | string | false | | | -| `healthy` | boolean | false | | | -| `icon_url` | string | false | | | -| `id` | string | false | | | -| `name` | string | false | | | -| `path_app_url` | string | false | | Path app URL is the URL to the base path for path apps. Optional unless wildcard_hostname is set. E.g. https://us.example.com | -| `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. | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------------------------------------------------------------------ | -------- | ------------ | ----------- | +| `active_users` | integer | false | | | +| `end_time` | string | false | | | +| `interval` | [codersdk.InsightsReportInterval](#codersdkinsightsreportinterval) | false | | | +| `start_time` | string | false | | | +| `template_ids` | array of string | false | | | -## codersdk.RegionsResponse-codersdk_Region +## codersdk.TemplateInsightsReport ```json { - "regions": [ + "active_users": 22, + "apps_usage": [ + { + "display_name": "Visual Studio Code", + "icon": "string", + "seconds": 80500, + "slug": "vscode", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "type": "builtin" + } + ], + "end_time": "2019-08-24T14:15:22Z", + "parameters_usage": [ { + "description": "string", "display_name": "string", - "healthy": true, - "icon_url": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", - "path_app_url": "string", - "wildcard_hostname": "string" + "options": [ + { + "description": "string", + "icon": "string", + "name": "string", + "value": "string" + } + ], + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "type": "string", + "values": [ + { + "count": 0, + "value": "string" + } + ] } - ] + ], + "start_time": "2019-08-24T14:15:22Z", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------------------------------------------- | -------- | ------------ | ----------- | -| `regions` | array of [codersdk.Region](#codersdkregion) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | --------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `active_users` | integer | false | | | +| `apps_usage` | array of [codersdk.TemplateAppUsage](#codersdktemplateappusage) | false | | | +| `end_time` | string | false | | | +| `parameters_usage` | array of [codersdk.TemplateParameterUsage](#codersdktemplateparameterusage) | false | | | +| `start_time` | string | false | | | +| `template_ids` | array of string | false | | | -## codersdk.RegionsResponse-codersdk_WorkspaceProxy +## codersdk.TemplateInsightsResponse ```json { - "regions": [ + "interval_reports": [ { - "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" + "active_users": 14, + "end_time": "2019-08-24T14:15:22Z", + "interval": "week", + "start_time": "2019-08-24T14:15:22Z", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] } - ] + ], + "report": { + "active_users": 22, + "apps_usage": [ + { + "display_name": "Visual Studio Code", + "icon": "string", + "seconds": 80500, + "slug": "vscode", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "type": "builtin" + } + ], + "end_time": "2019-08-24T14:15:22Z", + "parameters_usage": [ + { + "description": "string", + "display_name": "string", + "name": "string", + "options": [ + { + "description": "string", + "icon": "string", + "name": "string", + "value": "string" + } + ], + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "type": "string", + "values": [ + { + "count": 0, + "value": "string" + } + ] + } + ], + "start_time": "2019-08-24T14:15:22Z", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] + } } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ----------------------------------------------------------- | -------- | ------------ | ----------- | -| `regions` | array of [codersdk.WorkspaceProxy](#codersdkworkspaceproxy) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `interval_reports` | array of [codersdk.TemplateInsightsIntervalReport](#codersdktemplateinsightsintervalreport) | false | | | +| `report` | [codersdk.TemplateInsightsReport](#codersdktemplateinsightsreport) | false | | | -## codersdk.Replica +## codersdk.TemplateParameterUsage ```json { - "created_at": "2019-08-24T14:15:22Z", - "database_latency": 0, - "error": "string", - "hostname": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "region_id": 0, - "relay_address": "string" + "description": "string", + "display_name": "string", + "name": "string", + "options": [ + { + "description": "string", + "icon": "string", + "name": "string", + "value": "string" + } + ], + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "type": "string", + "values": [ + { + "count": 0, + "value": "string" + } + ] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------- | -------- | ------------ | ------------------------------------------------------------------ | -| `created_at` | string | false | | Created at is the timestamp when the replica was first seen. | -| `database_latency` | integer | false | | Database latency is the latency in microseconds to the database. | -| `error` | string | false | | Error is the replica error. | -| `hostname` | string | false | | Hostname is the hostname of the replica. | -| `id` | string | false | | ID is the unique identifier for the replica. | -| `region_id` | integer | false | | Region ID is the region of the replica. | -| `relay_address` | string | false | | Relay address is the accessible address to relay DERP connections. | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `description` | string | false | | | +| `display_name` | string | false | | | +| `name` | string | false | | | +| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | | +| `template_ids` | array of string | false | | | +| `type` | string | false | | | +| `values` | array of [codersdk.TemplateParameterValue](#codersdktemplateparametervalue) | false | | | -## codersdk.ResolveAutostartResponse +## codersdk.TemplateParameterValue ```json { - "parameter_mismatch": true + "count": 0, + "value": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------------- | ------- | -------- | ------------ | ----------- | -| `parameter_mismatch` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +| ------- | ------- | -------- | ------------ | ----------- | +| `count` | integer | false | | | +| `value` | string | false | | | -## codersdk.ResourceType +## codersdk.TemplateRole ```json -"template" +"admin" ``` ### Properties #### Enumerated Values -| Value | -| ---------------------------- | -| `template` | -| `template_version` | -| `user` | -| `workspace` | -| `workspace_build` | -| `git_ssh_key` | -| `api_key` | -| `group` | -| `license` | -| `convert_login` | -| `health_settings` | -| `workspace_proxy` | -| `organization` | -| `oauth2_provider_app` | -| `oauth2_provider_app_secret` | +| Value | +| ------- | +| `admin` | +| `use` | +| `` | -## codersdk.Response +## codersdk.TemplateUser ```json { - "detail": "string", - "message": "string", - "validations": [ + "avatar_url": "http://example.com", + "created_at": "2019-08-24T14:15:22Z", + "email": "user@example.com", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "last_seen_at": "2019-08-24T14:15:22Z", + "login_type": "", + "name": "string", + "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "role": "admin", + "roles": [ { - "detail": "string", - "field": "string" + "display_name": "string", + "name": "string" } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------- | ------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `detail` | string | false | | Detail is a debug message that provides further insight into why the action failed. This information can be technical and a regular golang err.Error() text. - "database: too many open connections" - "stat: too many open files" | -| `message` | string | false | | Message is an actionable message that depicts actions the request took. These messages should be fully formed sentences with proper punctuation. Examples: - "A user has been created." - "Failed to create a user." | -| `validations` | array of [codersdk.ValidationError](#codersdkvalidationerror) | false | | Validations are form field-specific friendly error messages. They will be shown on a form field in the UI. These can also be used to add additional context if there is a set of errors in the primary 'Message'. | - -## codersdk.Role - -```json -{ - "display_name": "string", - "name": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------- | ------ | -------- | ------------ | ----------- | -| `display_name` | string | false | | | -| `name` | string | false | | | - -## codersdk.SSHConfig - -```json -{ - "deploymentName": "string", - "sshconfigOptions": ["string"] + ], + "status": "active", + "theme_preference": "string", + "username": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | --------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------- | -| `deploymentName` | string | false | | Deploymentname is the config-ssh Hostname prefix | -| `sshconfigOptions` | array of string | false | | Sshconfigoptions are additional options to add to the ssh config file. This will override defaults. | - -## codersdk.SSHConfigResponse - -```json -{ - "hostname_prefix": "string", - "ssh_config_options": { - "property1": "string", - "property2": "string" - } -} -``` +| Name | Type | Required | Restrictions | Description | +| ------------------ | ---------------------------------------------- | -------- | ------------ | ----------- | +| `avatar_url` | string | false | | | +| `created_at` | string | true | | | +| `email` | string | true | | | +| `id` | string | true | | | +| `last_seen_at` | string | false | | | +| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | | +| `name` | string | false | | | +| `organization_ids` | array of string | false | | | +| `role` | [codersdk.TemplateRole](#codersdktemplaterole) | false | | | +| `roles` | array of [codersdk.Role](#codersdkrole) | false | | | +| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | | +| `theme_preference` | string | false | | | +| `username` | string | true | | | -### Properties +#### Enumerated Values -| Name | Type | Required | Restrictions | Description | -| -------------------- | ------ | -------- | ------------ | ----------- | -| `hostname_prefix` | string | false | | | -| `ssh_config_options` | object | false | | | -| » `[any property]` | string | false | | | +| Property | Value | +| -------- | ----------- | +| `role` | `admin` | +| `role` | `use` | +| `status` | `active` | +| `status` | `suspended` | -## codersdk.STUNReport +## codersdk.TemplateVersion ```json { - "canSTUN": true, - "enabled": true, - "error": "string" + "archived": true, + "created_at": "2019-08-24T14:15:22Z", + "created_by": { + "avatar_url": "http://example.com", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "username": "string" + }, + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "job": { + "canceled_at": "2019-08-24T14:15:22Z", + "completed_at": "2019-08-24T14:15:22Z", + "created_at": "2019-08-24T14:15:22Z", + "error": "string", + "error_code": "REQUIRED_TEMPLATE_VARIABLES", + "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, + "started_at": "2019-08-24T14:15:22Z", + "status": "pending", + "tags": { + "property1": "string", + "property2": "string" + }, + "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" + }, + "message": "string", + "name": "string", + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "readme": "string", + "template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc", + "updated_at": "2019-08-24T14:15:22Z", + "warnings": ["UNSUPPORTED_WORKSPACES"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------- | -------- | ------------ | ----------- | -| `canSTUN` | boolean | false | | | -| `enabled` | boolean | false | | | -| `error` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ----------------- | --------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `archived` | boolean | false | | | +| `created_at` | string | false | | | +| `created_by` | [codersdk.MinimalUser](#codersdkminimaluser) | false | | | +| `id` | string | false | | | +| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | | +| `message` | string | false | | | +| `name` | string | false | | | +| `organization_id` | string | false | | | +| `readme` | string | false | | | +| `template_id` | string | false | | | +| `updated_at` | string | false | | | +| `warnings` | array of [codersdk.TemplateVersionWarning](#codersdktemplateversionwarning) | false | | | -## codersdk.ServiceBannerConfig +## codersdk.TemplateVersionExternalAuth ```json { - "background_color": "string", - "enabled": true, - "message": "string" + "authenticate_url": "string", + "authenticated": true, + "display_icon": "string", + "display_name": "string", + "id": "string", + "optional": true, + "type": "string" } ``` @@ -5243,550 +4938,464 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in | Name | Type | Required | Restrictions | Description | | ------------------ | ------- | -------- | ------------ | ----------- | -| `background_color` | string | false | | | -| `enabled` | boolean | false | | | -| `message` | string | false | | | +| `authenticate_url` | string | false | | | +| `authenticated` | boolean | false | | | +| `display_icon` | string | false | | | +| `display_name` | string | false | | | +| `id` | string | false | | | +| `optional` | boolean | false | | | +| `type` | string | false | | | -## codersdk.SessionCountDeploymentStats +## codersdk.TemplateVersionParameter ```json { - "jetbrains": 0, - "reconnecting_pty": 0, - "ssh": 0, - "vscode": 0 + "default_value": "string", + "description": "string", + "description_plaintext": "string", + "display_name": "string", + "ephemeral": true, + "icon": "string", + "mutable": true, + "name": "string", + "options": [ + { + "description": "string", + "icon": "string", + "name": "string", + "value": "string" + } + ], + "required": true, + "type": "string", + "validation_error": "string", + "validation_max": 0, + "validation_min": 0, + "validation_monotonic": "increasing", + "validation_regex": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------- | -------- | ------------ | ----------- | -| `jetbrains` | integer | false | | | -| `reconnecting_pty` | integer | false | | | -| `ssh` | integer | false | | | -| `vscode` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| ----------------------- | ------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `default_value` | string | false | | | +| `description` | string | false | | | +| `description_plaintext` | string | false | | | +| `display_name` | string | false | | | +| `ephemeral` | boolean | false | | | +| `icon` | string | false | | | +| `mutable` | boolean | false | | | +| `name` | string | false | | | +| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | | +| `required` | boolean | false | | | +| `type` | string | false | | | +| `validation_error` | string | false | | | +| `validation_max` | integer | false | | | +| `validation_min` | integer | false | | | +| `validation_monotonic` | [codersdk.ValidationMonotonicOrder](#codersdkvalidationmonotonicorder) | false | | | +| `validation_regex` | string | false | | | -## codersdk.SupportConfig +#### Enumerated Values + +| Property | Value | +| ---------------------- | -------------- | +| `type` | `string` | +| `type` | `number` | +| `type` | `bool` | +| `type` | `list(string)` | +| `validation_monotonic` | `increasing` | +| `validation_monotonic` | `decreasing` | + +## codersdk.TemplateVersionParameterOption ```json { - "links": { - "value": [ - { - "icon": "bug", - "name": "string", - "target": "string" - } - ] - } + "description": "string", + "icon": "string", + "name": "string", + "value": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------- | ------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `links` | [serpent.Struct-array_codersdk_LinkConfig](#serpentstruct-array_codersdk_linkconfig) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------- | ------ | -------- | ------------ | ----------- | +| `description` | string | false | | | +| `icon` | string | false | | | +| `name` | string | false | | | +| `value` | string | false | | | -## codersdk.SwaggerConfig +## codersdk.TemplateVersionVariable ```json { - "enable": true + "default_value": "string", + "description": "string", + "name": "string", + "required": true, + "sensitive": true, + "type": "string", + "value": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | ------- | -------- | ------------ | ----------- | -| `enable` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +| --------------- | ------- | -------- | ------------ | ----------- | +| `default_value` | string | false | | | +| `description` | string | false | | | +| `name` | string | false | | | +| `required` | boolean | false | | | +| `sensitive` | boolean | false | | | +| `type` | string | false | | | +| `value` | string | false | | | -## codersdk.TLSConfig +#### Enumerated Values + +| Property | Value | +| -------- | -------- | +| `type` | `string` | +| `type` | `number` | +| `type` | `bool` | + +## codersdk.TemplateVersionWarning ```json -{ - "address": { - "host": "string", - "port": "string" - }, - "allow_insecure_ciphers": true, - "cert_file": ["string"], - "client_auth": "string", - "client_ca_file": "string", - "client_cert_file": "string", - "client_key_file": "string", - "enable": true, - "key_file": ["string"], - "min_version": "string", - "redirect_http": true, - "supported_ciphers": ["string"] -} +"UNSUPPORTED_WORKSPACES" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------------ | ------------------------------------ | -------- | ------------ | ----------- | -| `address` | [serpent.HostPort](#serpenthostport) | false | | | -| `allow_insecure_ciphers` | boolean | false | | | -| `cert_file` | array of string | false | | | -| `client_auth` | string | false | | | -| `client_ca_file` | string | false | | | -| `client_cert_file` | string | false | | | -| `client_key_file` | string | false | | | -| `enable` | boolean | false | | | -| `key_file` | array of string | false | | | -| `min_version` | string | false | | | -| `redirect_http` | boolean | false | | | -| `supported_ciphers` | array of string | false | | | +#### Enumerated Values -## codersdk.TelemetryConfig +| Value | +| ------------------------ | +| `UNSUPPORTED_WORKSPACES` | + +## codersdk.TokenConfig ```json { - "enable": true, - "trace": true, - "url": { - "forceQuery": true, - "fragment": "string", - "host": "string", - "omitHost": true, - "opaque": "string", - "path": "string", - "rawFragment": "string", - "rawPath": "string", - "rawQuery": "string", - "scheme": "string", - "user": {} - } + "max_token_lifetime": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | -------------------------- | -------- | ------------ | ----------- | -| `enable` | boolean | false | | | -| `trace` | boolean | false | | | -| `url` | [serpent.URL](#serpenturl) | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------- | -------- | ------------ | ----------- | +| `max_token_lifetime` | integer | false | | | -## codersdk.Template +## codersdk.TraceConfig ```json { - "active_user_count": 0, - "active_version_id": "eae64611-bd53-4a80-bb77-df1e432c0fbc", - "activity_bump_ms": 0, - "allow_user_autostart": true, - "allow_user_autostop": true, - "allow_user_cancel_workspace_jobs": true, - "autostart_requirement": { - "days_of_week": ["monday"] - }, - "autostop_requirement": { - "days_of_week": ["monday"], - "weeks": 0 - }, - "build_time_stats": { - "property1": { - "p50": 123, - "p95": 146 - }, - "property2": { - "p50": 123, - "p95": 146 - } - }, - "created_at": "2019-08-24T14:15:22Z", - "created_by_id": "9377d689-01fb-4abf-8450-3368d2c1924f", - "created_by_name": "string", - "default_ttl_ms": 0, - "deprecated": true, - "deprecation_message": "string", - "description": "string", - "display_name": "string", - "failure_ttl_ms": 0, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "max_port_share_level": "owner", - "name": "string", - "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", - "provisioner": "terraform", - "require_active_version": true, - "time_til_dormant_autodelete_ms": 0, - "time_til_dormant_ms": 0, - "updated_at": "2019-08-24T14:15:22Z" + "capture_logs": true, + "data_dog": true, + "enable": true, + "honeycomb_api_key": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `active_user_count` | integer | false | | Active user count is set to -1 when loading. | -| `active_version_id` | string | false | | | -| `activity_bump_ms` | integer | false | | | -| `allow_user_autostart` | boolean | false | | Allow user autostart and AllowUserAutostop are enterprise-only. Their values are only used if your license is entitled to use the advanced template scheduling feature. | -| `allow_user_autostop` | boolean | false | | | -| `allow_user_cancel_workspace_jobs` | boolean | false | | | -| `autostart_requirement` | [codersdk.TemplateAutostartRequirement](#codersdktemplateautostartrequirement) | false | | | -| `autostop_requirement` | [codersdk.TemplateAutostopRequirement](#codersdktemplateautostoprequirement) | false | | Autostop requirement and AutostartRequirement are enterprise features. Its value is only used if your license is entitled to use the advanced template scheduling feature. | -| `build_time_stats` | [codersdk.TemplateBuildTimeStats](#codersdktemplatebuildtimestats) | false | | | -| `created_at` | string | false | | | -| `created_by_id` | string | false | | | -| `created_by_name` | string | false | | | -| `default_ttl_ms` | integer | false | | | -| `deprecated` | boolean | false | | | -| `deprecation_message` | string | false | | | -| `description` | string | false | | | -| `display_name` | string | false | | | -| `failure_ttl_ms` | integer | false | | Failure ttl ms TimeTilDormantMillis, and TimeTilDormantAutoDeleteMillis are enterprise-only. Their values are used if your license is entitled to use the advanced template scheduling feature. | -| `icon` | string | false | | | -| `id` | string | false | | | -| `max_port_share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | | -| `name` | string | false | | | -| `organization_id` | string | false | | | -| `provisioner` | string | false | | | -| `require_active_version` | boolean | false | | Require active version mandates that workspaces are built with the active template version. | -| `time_til_dormant_autodelete_ms` | integer | false | | | -| `time_til_dormant_ms` | integer | false | | | -| `updated_at` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------- | ------- | -------- | ------------ | ----------- | +| `capture_logs` | boolean | false | | | +| `data_dog` | boolean | false | | | +| `enable` | boolean | false | | | +| `honeycomb_api_key` | string | false | | | -#### Enumerated Values +## codersdk.TransitionStats -| Property | Value | -| ------------- | ----------- | -| `provisioner` | `terraform` | +```json +{ + "p50": 123, + "p95": 146 +} +``` -## codersdk.TemplateAppUsage +### Properties + +| Name | Type | Required | Restrictions | Description | +| ----- | ------- | -------- | ------------ | ----------- | +| `p50` | integer | false | | | +| `p95` | integer | false | | | + +## codersdk.UpdateActiveTemplateVersion ```json { - "display_name": "Visual Studio Code", - "icon": "string", - "seconds": 80500, - "slug": "vscode", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "type": "builtin" + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------------------------------------------------------ | -------- | ------------ | ----------- | -| `display_name` | string | false | | | -| `icon` | string | false | | | -| `seconds` | integer | false | | | -| `slug` | string | false | | | -| `template_ids` | array of string | false | | | -| `type` | [codersdk.TemplateAppsType](#codersdktemplateappstype) | false | | | +| Name | Type | Required | Restrictions | Description | +| ---- | ------ | -------- | ------------ | ----------- | +| `id` | string | true | | | -## codersdk.TemplateAppsType +## codersdk.UpdateAppearanceConfig ```json -"builtin" +{ + "application_name": "string", + "logo_url": "string", + "service_banner": { + "background_color": "string", + "enabled": true, + "message": "string" + } +} ``` ### Properties -#### Enumerated Values - -| Value | -| --------- | -| `builtin` | -| `app` | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------------------------------------------------------------ | -------- | ------------ | ----------- | +| `application_name` | string | false | | | +| `logo_url` | string | false | | | +| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | | -## codersdk.TemplateAutostartRequirement +## codersdk.UpdateCheckResponse ```json { - "days_of_week": ["monday"] + "current": true, + "url": "string", + "version": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | --------------- | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -| `days_of_week` | array of string | false | | Days of week is a list of days of the week in which autostart is allowed to happen. If no days are specified, autostart is not allowed. | +| Name | Type | Required | Restrictions | Description | +| --------- | ------- | -------- | ------------ | ----------------------------------------------------------------------- | +| `current` | boolean | false | | Current indicates whether the server version is the same as the latest. | +| `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.TemplateAutostopRequirement +## codersdk.UpdateRoles ```json { - "days_of_week": ["monday"], - "weeks": 0 + "roles": ["string"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------------------------------------------------------------------------- | --------------- | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `days_of_week` | array of string | false | | Days of week is a list of days of the week on which restarts are required. Restarts happen within the user's quiet hours (in their configured timezone). If no days are specified, restarts are not required. Weekdays cannot be specified twice. | -| Restarts will only happen on weekdays in this list on weeks which line up with Weeks. | -| `weeks` | integer | false | | Weeks is the number of weeks between required restarts. Weeks are synced across all workspaces (and Coder deployments) using modulo math on a hardcoded epoch week of January 2nd, 2023 (the first Monday of 2023). Values of 0 or 1 indicate weekly restarts. Values of 2 indicate fortnightly restarts, etc. | +| Name | Type | Required | Restrictions | Description | +| ------- | --------------- | -------- | ------------ | ----------- | +| `roles` | array of string | false | | | -## codersdk.TemplateBuildTimeStats +## codersdk.UpdateTemplateACL ```json { - "property1": { - "p50": 123, - "p95": 146 + "group_perms": { + "8bd26b20-f3e8-48be-a903-46bb920cf671": "use", + ">": "admin" }, - "property2": { - "p50": 123, - "p95": 146 + "user_perms": { + "4df59e74-c027-470b-ab4d-cbba8963a5e9": "use", + "": "admin" } } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------- | ---------------------------------------------------- | -------- | ------------ | ----------- | -| `[any property]` | [codersdk.TransitionStats](#codersdktransitionstats) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ---------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | +| `group_perms` | object | false | | Group perms should be a mapping of group ID to role. | +| » `[any property]` | [codersdk.TemplateRole](#codersdktemplaterole) | false | | | +| `user_perms` | object | false | | User perms should be a mapping of user ID to role. The user ID must be the uuid of the user, not a username or email address. | +| » `[any property]` | [codersdk.TemplateRole](#codersdktemplaterole) | false | | | -## codersdk.TemplateExample +## codersdk.UpdateUserAppearanceSettingsRequest ```json { - "description": "string", - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "markdown": "string", - "name": "string", - "tags": ["string"], - "url": "string" + "theme_preference": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | --------------- | -------- | ------------ | ----------- | -| `description` | string | false | | | -| `icon` | string | false | | | -| `id` | string | false | | | -| `markdown` | string | false | | | -| `name` | string | false | | | -| `tags` | array of string | false | | | -| `url` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------ | -------- | ------------ | ----------- | +| `theme_preference` | string | true | | | -## codersdk.TemplateInsightsIntervalReport +## codersdk.UpdateUserPasswordRequest ```json { - "active_users": 14, - "end_time": "2019-08-24T14:15:22Z", - "interval": "week", - "start_time": "2019-08-24T14:15:22Z", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] + "old_password": "string", + "password": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `active_users` | integer | false | | | -| `end_time` | string | false | | | -| `interval` | [codersdk.InsightsReportInterval](#codersdkinsightsreportinterval) | false | | | -| `start_time` | string | false | | | -| `template_ids` | array of string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------ | -------- | ------------ | ----------- | +| `old_password` | string | false | | | +| `password` | string | true | | | -## codersdk.TemplateInsightsReport +## codersdk.UpdateUserProfileRequest ```json { - "active_users": 22, - "apps_usage": [ - { - "display_name": "Visual Studio Code", - "icon": "string", - "seconds": 80500, - "slug": "vscode", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "type": "builtin" - } - ], - "end_time": "2019-08-24T14:15:22Z", - "parameters_usage": [ - { - "description": "string", - "display_name": "string", - "name": "string", - "options": [ - { - "description": "string", - "icon": "string", - "name": "string", - "value": "string" - } - ], - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "type": "string", - "values": [ - { - "count": 0, - "value": "string" - } - ] - } - ], - "start_time": "2019-08-24T14:15:22Z", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] + "name": "string", + "username": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------- | ------ | -------- | ------------ | ----------- | +| `name` | string | false | | | +| `username` | string | true | | | + +## codersdk.UpdateUserQuietHoursScheduleRequest + +```json +{ + "schedule": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------- | ------ | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `schedule` | string | true | | Schedule is a cron expression that defines when the user's quiet hours window is. Schedule must not be empty. For new users, the schedule is set to 2am in their browser or computer's timezone. The schedule denotes the beginning of a 4 hour window where the workspace is allowed to automatically stop or restart due to maintenance or template schedule. | + +The schedule must be daily with a single time, and should have a timezone specified via a CRON_TZ prefix (otherwise UTC will be used). +If the schedule is empty, the user will be updated to use the default schedule.| + +## codersdk.UpdateWorkspaceAutomaticUpdatesRequest + +```json +{ + "automatic_updates": "always" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | --------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `active_users` | integer | false | | | -| `apps_usage` | array of [codersdk.TemplateAppUsage](#codersdktemplateappusage) | false | | | -| `end_time` | string | false | | | -| `parameters_usage` | array of [codersdk.TemplateParameterUsage](#codersdktemplateparameterusage) | false | | | -| `start_time` | string | false | | | -| `template_ids` | array of string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------- | ------------------------------------------------------ | -------- | ------------ | ----------- | +| `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | | -## codersdk.TemplateInsightsResponse +## codersdk.UpdateWorkspaceAutostartRequest ```json { - "interval_reports": [ - { - "active_users": 14, - "end_time": "2019-08-24T14:15:22Z", - "interval": "week", - "start_time": "2019-08-24T14:15:22Z", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] - } - ], - "report": { - "active_users": 22, - "apps_usage": [ - { - "display_name": "Visual Studio Code", - "icon": "string", - "seconds": 80500, - "slug": "vscode", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "type": "builtin" - } - ], - "end_time": "2019-08-24T14:15:22Z", - "parameters_usage": [ - { - "description": "string", - "display_name": "string", - "name": "string", - "options": [ - { - "description": "string", - "icon": "string", - "name": "string", - "value": "string" - } - ], - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "type": "string", - "values": [ - { - "count": 0, - "value": "string" - } - ] - } - ], - "start_time": "2019-08-24T14:15:22Z", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"] - } + "schedule": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `interval_reports` | array of [codersdk.TemplateInsightsIntervalReport](#codersdktemplateinsightsintervalreport) | false | | | -| `report` | [codersdk.TemplateInsightsReport](#codersdktemplateinsightsreport) | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------- | ------ | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `schedule` | string | false | | Schedule is expected to be of the form `CRON_TZ= * * ` Example: `CRON_TZ=US/Central 30 9 * * 1-5` represents 0930 in the timezone US/Central on weekdays (Mon-Fri). `CRON_TZ` defaults to UTC if not present. | -## codersdk.TemplateParameterUsage +## codersdk.UpdateWorkspaceDormancy ```json { - "description": "string", - "display_name": "string", - "name": "string", - "options": [ - { - "description": "string", - "icon": "string", - "name": "string", - "value": "string" - } - ], - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "type": "string", - "values": [ - { - "count": 0, - "value": "string" - } - ] + "dormant": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `description` | string | false | | | -| `display_name` | string | false | | | -| `name` | string | false | | | -| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | | -| `template_ids` | array of string | false | | | -| `type` | string | false | | | -| `values` | array of [codersdk.TemplateParameterValue](#codersdktemplateparametervalue) | false | | | +| Name | Type | Required | Restrictions | Description | +| --------- | ------- | -------- | ------------ | ----------- | +| `dormant` | boolean | false | | | -## codersdk.TemplateParameterValue +## codersdk.UpdateWorkspaceRequest ```json { - "count": 0, - "value": "string" + "name": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------- | ------- | -------- | ------------ | ----------- | -| `count` | integer | false | | | -| `value` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------ | ------ | -------- | ------------ | ----------- | +| `name` | string | false | | | -## codersdk.TemplateRole +## codersdk.UpdateWorkspaceTTLRequest ```json -"admin" +{ + "ttl_ms": 0 +} ``` ### Properties +| Name | Type | Required | Restrictions | Description | +| -------- | ------- | -------- | ------------ | ----------- | +| `ttl_ms` | integer | false | | | + +## codersdk.UploadResponse + +```json +{ + "hash": "19686d84-b10d-4f90-b18e-84fd3fa038fd" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------ | ------ | -------- | ------------ | ----------- | +| `hash` | string | false | | | + +## codersdk.UpsertWorkspaceAgentPortShareRequest + +```json +{ + "agent_name": "string", + "port": 0, + "protocol": "http", + "share_level": "owner" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------- | ------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | +| `agent_name` | string | false | | | +| `port` | integer | false | | | +| `protocol` | [codersdk.WorkspaceAgentPortShareProtocol](#codersdkworkspaceagentportshareprotocol) | false | | | +| `share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | | + #### Enumerated Values -| Value | -| ------- | -| `admin` | -| `use` | -| `` | +| Property | Value | +| ------------- | --------------- | +| `protocol` | `http` | +| `protocol` | `https` | +| `share_level` | `owner` | +| `share_level` | `authenticated` | +| `share_level` | `public` | -## codersdk.TemplateUser +## codersdk.User ```json { @@ -5798,7 +5407,6 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in "login_type": "", "name": "string", "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "role": "admin", "roles": [ { "display_name": "string", @@ -5813,566 +5421,839 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ---------------------------------------------- | -------- | ------------ | ----------- | -| `avatar_url` | string | false | | | -| `created_at` | string | true | | | -| `email` | string | true | | | -| `id` | string | true | | | -| `last_seen_at` | string | false | | | -| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | | -| `name` | string | false | | | -| `organization_ids` | array of string | false | | | -| `role` | [codersdk.TemplateRole](#codersdktemplaterole) | false | | | -| `roles` | array of [codersdk.Role](#codersdkrole) | false | | | -| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | | -| `theme_preference` | string | false | | | -| `username` | string | true | | | - -#### Enumerated Values - -| Property | Value | -| -------- | ----------- | -| `role` | `admin` | -| `role` | `use` | -| `status` | `active` | -| `status` | `suspended` | - -## codersdk.TemplateVersion - -```json -{ - "archived": true, - "created_at": "2019-08-24T14:15:22Z", - "created_by": { - "avatar_url": "http://example.com", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "username": "string" - }, - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "job": { - "canceled_at": "2019-08-24T14:15:22Z", - "completed_at": "2019-08-24T14:15:22Z", - "created_at": "2019-08-24T14:15:22Z", - "error": "string", - "error_code": "REQUIRED_TEMPLATE_VARIABLES", - "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "queue_position": 0, - "queue_size": 0, - "started_at": "2019-08-24T14:15:22Z", - "status": "pending", - "tags": { - "property1": "string", - "property2": "string" - }, - "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" - }, - "message": "string", - "name": "string", - "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", - "readme": "string", - "template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc", - "updated_at": "2019-08-24T14:15:22Z", - "warnings": ["UNSUPPORTED_WORKSPACES"] -} -``` +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------------------------------------------ | -------- | ------------ | ----------- | +| `avatar_url` | string | false | | | +| `created_at` | string | true | | | +| `email` | string | true | | | +| `id` | string | true | | | +| `last_seen_at` | string | false | | | +| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | | +| `name` | string | false | | | +| `organization_ids` | array of string | false | | | +| `roles` | array of [codersdk.Role](#codersdkrole) | false | | | +| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | | +| `theme_preference` | string | false | | | +| `username` | string | true | | | -### Properties +#### Enumerated Values -| Name | Type | Required | Restrictions | Description | -| ----------------- | --------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `archived` | boolean | false | | | -| `created_at` | string | false | | | -| `created_by` | [codersdk.MinimalUser](#codersdkminimaluser) | false | | | -| `id` | string | false | | | -| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | | -| `message` | string | false | | | -| `name` | string | false | | | -| `organization_id` | string | false | | | -| `readme` | string | false | | | -| `template_id` | string | false | | | -| `updated_at` | string | false | | | -| `warnings` | array of [codersdk.TemplateVersionWarning](#codersdktemplateversionwarning) | false | | | +| Property | Value | +| -------- | ----------- | +| `status` | `active` | +| `status` | `suspended` | -## codersdk.TemplateVersionExternalAuth +## codersdk.UserActivity ```json { - "authenticate_url": "string", - "authenticated": true, - "display_icon": "string", - "display_name": "string", - "id": "string", - "optional": true, - "type": "string" + "avatar_url": "http://example.com", + "seconds": 80500, + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", + "username": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------- | -------- | ------------ | ----------- | -| `authenticate_url` | string | false | | | -| `authenticated` | boolean | false | | | -| `display_icon` | string | false | | | -| `display_name` | string | false | | | -| `id` | string | false | | | -| `optional` | boolean | false | | | -| `type` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | --------------- | -------- | ------------ | ----------- | +| `avatar_url` | string | false | | | +| `seconds` | integer | false | | | +| `template_ids` | array of string | false | | | +| `user_id` | string | false | | | +| `username` | string | false | | | -## codersdk.TemplateVersionParameter +## codersdk.UserActivityInsightsReport ```json { - "default_value": "string", - "description": "string", - "description_plaintext": "string", - "display_name": "string", - "ephemeral": true, - "icon": "string", - "mutable": true, - "name": "string", - "options": [ + "end_time": "2019-08-24T14:15:22Z", + "start_time": "2019-08-24T14:15:22Z", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "users": [ { - "description": "string", - "icon": "string", - "name": "string", - "value": "string" + "avatar_url": "http://example.com", + "seconds": 80500, + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", + "username": "string" } - ], - "required": true, - "type": "string", - "validation_error": "string", - "validation_max": 0, - "validation_min": 0, - "validation_monotonic": "increasing", - "validation_regex": "string" + ] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ----------------------- | ------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `default_value` | string | false | | | -| `description` | string | false | | | -| `description_plaintext` | string | false | | | -| `display_name` | string | false | | | -| `ephemeral` | boolean | false | | | -| `icon` | string | false | | | -| `mutable` | boolean | false | | | -| `name` | string | false | | | -| `options` | array of [codersdk.TemplateVersionParameterOption](#codersdktemplateversionparameteroption) | false | | | -| `required` | boolean | false | | | -| `type` | string | false | | | -| `validation_error` | string | false | | | -| `validation_max` | integer | false | | | -| `validation_min` | integer | false | | | -| `validation_monotonic` | [codersdk.ValidationMonotonicOrder](#codersdkvalidationmonotonicorder) | false | | | -| `validation_regex` | string | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------------------- | -------------- | -| `type` | `string` | -| `type` | `number` | -| `type` | `bool` | -| `type` | `list(string)` | -| `validation_monotonic` | `increasing` | -| `validation_monotonic` | `decreasing` | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------------------------------------------------------- | -------- | ------------ | ----------- | +| `end_time` | string | false | | | +| `start_time` | string | false | | | +| `template_ids` | array of string | false | | | +| `users` | array of [codersdk.UserActivity](#codersdkuseractivity) | false | | | -## codersdk.TemplateVersionParameterOption +## codersdk.UserActivityInsightsResponse ```json { - "description": "string", - "icon": "string", - "name": "string", - "value": "string" + "report": { + "end_time": "2019-08-24T14:15:22Z", + "start_time": "2019-08-24T14:15:22Z", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "users": [ + { + "avatar_url": "http://example.com", + "seconds": 80500, + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", + "username": "string" + } + ] + } } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | ------ | -------- | ------------ | ----------- | -| `description` | string | false | | | -| `icon` | string | false | | | -| `name` | string | false | | | -| `value` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------- | -------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `report` | [codersdk.UserActivityInsightsReport](#codersdkuseractivityinsightsreport) | false | | | -## codersdk.TemplateVersionVariable +## codersdk.UserLatency ```json { - "default_value": "string", - "description": "string", - "name": "string", - "required": true, - "sensitive": true, - "type": "string", - "value": "string" + "avatar_url": "http://example.com", + "latency_ms": { + "p50": 31.312, + "p95": 119.832 + }, + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", + "username": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------------- | ------- | -------- | ------------ | ----------- | -| `default_value` | string | false | | | -| `description` | string | false | | | -| `name` | string | false | | | -| `required` | boolean | false | | | -| `sensitive` | boolean | false | | | -| `type` | string | false | | | -| `value` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | -------------------------------------------------------- | -------- | ------------ | ----------- | +| `avatar_url` | string | false | | | +| `latency_ms` | [codersdk.ConnectionLatency](#codersdkconnectionlatency) | false | | | +| `template_ids` | array of string | false | | | +| `user_id` | string | false | | | +| `username` | string | false | | | -#### Enumerated Values +## codersdk.UserLatencyInsightsReport -| Property | Value | -| -------- | -------- | -| `type` | `string` | -| `type` | `number` | -| `type` | `bool` | +```json +{ + "end_time": "2019-08-24T14:15:22Z", + "start_time": "2019-08-24T14:15:22Z", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "users": [ + { + "avatar_url": "http://example.com", + "latency_ms": { + "p50": 31.312, + "p95": 119.832 + }, + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", + "username": "string" + } + ] +} +``` -## codersdk.TemplateVersionWarning +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------- | ----------------------------------------------------- | -------- | ------------ | ----------- | +| `end_time` | string | false | | | +| `start_time` | string | false | | | +| `template_ids` | array of string | false | | | +| `users` | array of [codersdk.UserLatency](#codersdkuserlatency) | false | | | + +## codersdk.UserLatencyInsightsResponse ```json -"UNSUPPORTED_WORKSPACES" +{ + "report": { + "end_time": "2019-08-24T14:15:22Z", + "start_time": "2019-08-24T14:15:22Z", + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "users": [ + { + "avatar_url": "http://example.com", + "latency_ms": { + "p50": 31.312, + "p95": 119.832 + }, + "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", + "username": "string" + } + ] + } +} ``` ### Properties -#### Enumerated Values - -| Value | -| ------------------------ | -| `UNSUPPORTED_WORKSPACES` | +| Name | Type | Required | Restrictions | Description | +| -------- | ------------------------------------------------------------------------ | -------- | ------------ | ----------- | +| `report` | [codersdk.UserLatencyInsightsReport](#codersdkuserlatencyinsightsreport) | false | | | -## codersdk.TokenConfig +## codersdk.UserLoginType ```json { - "max_token_lifetime": 0 + "login_type": "" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------------- | ------- | -------- | ------------ | ----------- | -| `max_token_lifetime` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------ | ---------------------------------------- | -------- | ------------ | ----------- | +| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | | -## codersdk.TraceConfig +## codersdk.UserParameter ```json { - "capture_logs": true, - "data_dog": true, - "enable": true, - "honeycomb_api_key": "string" + "name": "string", + "value": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------- | ------- | -------- | ------------ | ----------- | -| `capture_logs` | boolean | false | | | -| `data_dog` | boolean | false | | | -| `enable` | boolean | false | | | -| `honeycomb_api_key` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------- | ------ | -------- | ------------ | ----------- | +| `name` | string | false | | | +| `value` | string | false | | | -## codersdk.TransitionStats +## codersdk.UserQuietHoursScheduleConfig ```json { - "p50": 123, - "p95": 146 + "allow_user_custom": true, + "default_schedule": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ----- | ------- | -------- | ------------ | ----------- | -| `p50` | integer | false | | | -| `p95` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------- | ------- | -------- | ------------ | ----------- | +| `allow_user_custom` | boolean | false | | | +| `default_schedule` | string | false | | | -## codersdk.UpdateActiveTemplateVersion +## codersdk.UserQuietHoursScheduleResponse ```json { - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08" + "next": "2019-08-24T14:15:22Z", + "raw_schedule": "string", + "time": "string", + "timezone": "string", + "user_can_set": true, + "user_set": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---- | ------ | -------- | ------------ | ----------- | -| `id` | string | true | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------- | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `next` | string | false | | Next is the next time that the quiet hours window will start. | +| `raw_schedule` | string | false | | | +| `time` | string | false | | Time is the time of day that the quiet hours window starts in the given Timezone each day. | +| `timezone` | string | false | | raw format from the cron expression, UTC if unspecified | +| `user_can_set` | boolean | false | | User can set is true if the user is allowed to set their own quiet hours schedule. If false, the user cannot set a custom schedule and the default schedule will always be used. | +| `user_set` | boolean | false | | User set is true if the user has set their own quiet hours schedule. If false, the user is using the default schedule. | -## codersdk.UpdateAppearanceConfig +## codersdk.UserStatus ```json -{ - "application_name": "string", - "logo_url": "string", - "service_banner": { - "background_color": "string", - "enabled": true, - "message": "string" - } -} +"active" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------------------------------------------------------------ | -------- | ------------ | ----------- | -| `application_name` | string | false | | | -| `logo_url` | string | false | | | -| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | | +#### Enumerated Values -## codersdk.UpdateCheckResponse +| Value | +| ----------- | +| `active` | +| `dormant` | +| `suspended` | + +## codersdk.ValidationError ```json { - "current": true, - "url": "string", - "version": "string" + "detail": "string", + "field": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------- | -------- | ------------ | ----------------------------------------------------------------------- | -| `current` | boolean | false | | Current indicates whether the server version is the same as the latest. | -| `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. | +| Name | Type | Required | Restrictions | Description | +| -------- | ------ | -------- | ------------ | ----------- | +| `detail` | string | true | | | +| `field` | string | true | | | -## codersdk.UpdateHealthSettings +## codersdk.ValidationMonotonicOrder ```json -{ - "dismissed_healthchecks": ["DERP"] -} +"increasing" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------------ | --------------------------------------------------------- | -------- | ------------ | ----------- | -| `dismissed_healthchecks` | array of [codersdk.HealthSection](#codersdkhealthsection) | false | | | +#### Enumerated Values -## codersdk.UpdateRoles +| Value | +| ------------ | +| `increasing` | +| `decreasing` | + +## codersdk.VariableValue ```json { - "roles": ["string"] + "name": "string", + "value": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------- | --------------- | -------- | ------------ | ----------- | -| `roles` | array of string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------- | ------ | -------- | ------------ | ----------- | +| `name` | string | false | | | +| `value` | string | false | | | -## codersdk.UpdateTemplateACL +## codersdk.Workspace ```json { - "group_perms": { - "8bd26b20-f3e8-48be-a903-46bb920cf671": "use", - ">": "admin" + "allow_renames": true, + "automatic_updates": "always", + "autostart_schedule": "string", + "created_at": "2019-08-24T14:15:22Z", + "deleting_at": "2019-08-24T14:15:22Z", + "dormant_at": "2019-08-24T14:15:22Z", + "favorite": true, + "health": { + "failing_agents": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "healthy": false + }, + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "last_used_at": "2019-08-24T14:15:22Z", + "latest_build": { + "build_number": 0, + "created_at": "2019-08-24T14:15:22Z", + "daily_cost": 0, + "deadline": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", + "initiator_name": "string", + "job": { + "canceled_at": "2019-08-24T14:15:22Z", + "completed_at": "2019-08-24T14:15:22Z", + "created_at": "2019-08-24T14:15:22Z", + "error": "string", + "error_code": "REQUIRED_TEMPLATE_VARIABLES", + "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, + "started_at": "2019-08-24T14:15:22Z", + "status": "pending", + "tags": { + "property1": "string", + "property2": "string" + }, + "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" + }, + "max_deadline": "2019-08-24T14:15:22Z", + "reason": "initiator", + "resources": [ + { + "agents": [ + { + "api_version": "string", + "apps": [ + { + "command": "string", + "display_name": "string", + "external": true, + "health": "disabled", + "healthcheck": { + "interval": 0, + "threshold": 0, + "url": "string" + }, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "sharing_level": "owner", + "slug": "string", + "subdomain": true, + "subdomain_name": "string", + "url": "string" + } + ], + "architecture": "string", + "connection_timeout_seconds": 0, + "created_at": "2019-08-24T14:15:22Z", + "directory": "string", + "disconnected_at": "2019-08-24T14:15:22Z", + "display_apps": ["vscode"], + "environment_variables": { + "property1": "string", + "property2": "string" + }, + "expanded_directory": "string", + "first_connected_at": "2019-08-24T14:15:22Z", + "health": { + "healthy": false, + "reason": "agent has lost connection" + }, + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "instance_id": "string", + "last_connected_at": "2019-08-24T14:15:22Z", + "latency": { + "property1": { + "latency_ms": 0, + "preferred": true + }, + "property2": { + "latency_ms": 0, + "preferred": true + } + }, + "lifecycle_state": "created", + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], + "logs_length": 0, + "logs_overflowed": true, + "name": "string", + "operating_system": "string", + "ready_at": "2019-08-24T14:15:22Z", + "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], + "started_at": "2019-08-24T14:15:22Z", + "startup_script_behavior": "blocking", + "status": "connecting", + "subsystems": ["envbox"], + "troubleshooting_url": "string", + "updated_at": "2019-08-24T14:15:22Z", + "version": "string" + } + ], + "created_at": "2019-08-24T14:15:22Z", + "daily_cost": 0, + "hide": true, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", + "metadata": [ + { + "key": "string", + "sensitive": true, + "value": "string" + } + ], + "name": "string", + "type": "string", + "workspace_transition": "start" + } + ], + "status": "pending", + "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", + "template_version_name": "string", + "transition": "start", + "updated_at": "2019-08-24T14:15:22Z", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9", + "workspace_name": "string", + "workspace_owner_avatar_url": "string", + "workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7", + "workspace_owner_name": "string" }, - "user_perms": { - "4df59e74-c027-470b-ab4d-cbba8963a5e9": "use", - "": "admin" - } -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------ | ---------------------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | -| `group_perms` | object | false | | Group perms should be a mapping of group ID to role. | -| » `[any property]` | [codersdk.TemplateRole](#codersdktemplaterole) | false | | | -| `user_perms` | object | false | | User perms should be a mapping of user ID to role. The user ID must be the uuid of the user, not a username or email address. | -| » `[any property]` | [codersdk.TemplateRole](#codersdktemplaterole) | false | | | - -## codersdk.UpdateUserAppearanceSettingsRequest - -```json -{ - "theme_preference": "string" + "name": "string", + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "outdated": true, + "owner_avatar_url": "string", + "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", + "owner_name": "string", + "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", + "template_allow_user_cancel_workspace_jobs": true, + "template_display_name": "string", + "template_icon": "string", + "template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc", + "template_name": "string", + "template_require_active_version": true, + "ttl_ms": 0, + "updated_at": "2019-08-24T14:15:22Z" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------ | -------- | ------------ | ----------- | -| `theme_preference` | string | true | | | - -## codersdk.UpdateUserPasswordRequest - -```json -{ - "old_password": "string", - "password": "string" -} -``` +| Name | Type | Required | Restrictions | Description | +| ------------------------------------------- | ------------------------------------------------------ | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `allow_renames` | boolean | false | | | +| `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | | +| `autostart_schedule` | string | false | | | +| `created_at` | string | false | | | +| `deleting_at` | string | false | | Deleting at indicates the time at which the workspace will be permanently deleted. A workspace is eligible for deletion if it is dormant (a non-nil dormant_at value) and a value has been specified for time_til_dormant_autodelete on its template. | +| `dormant_at` | string | false | | Dormant at being non-nil indicates a workspace that is dormant. A dormant workspace is no longer accessible must be activated. It is subject to deletion if it breaches the duration of the time*til* field on its template. | +| `favorite` | boolean | false | | | +| `health` | [codersdk.WorkspaceHealth](#codersdkworkspacehealth) | false | | Health shows the health of the workspace and information about what is causing an unhealthy status. | +| `id` | string | false | | | +| `last_used_at` | string | false | | | +| `latest_build` | [codersdk.WorkspaceBuild](#codersdkworkspacebuild) | false | | | +| `name` | string | false | | | +| `organization_id` | string | false | | | +| `outdated` | boolean | false | | | +| `owner_avatar_url` | string | false | | | +| `owner_id` | string | false | | | +| `owner_name` | string | false | | | +| `template_active_version_id` | string | false | | | +| `template_allow_user_cancel_workspace_jobs` | boolean | false | | | +| `template_display_name` | string | false | | | +| `template_icon` | string | false | | | +| `template_id` | string | false | | | +| `template_name` | string | false | | | +| `template_require_active_version` | boolean | false | | | +| `ttl_ms` | integer | false | | | +| `updated_at` | string | false | | | -### Properties +#### Enumerated Values -| Name | Type | Required | Restrictions | Description | -| -------------- | ------ | -------- | ------------ | ----------- | -| `old_password` | string | false | | | -| `password` | string | true | | | +| Property | Value | +| ------------------- | -------- | +| `automatic_updates` | `always` | +| `automatic_updates` | `never` | -## codersdk.UpdateUserProfileRequest +## codersdk.WorkspaceAgent ```json { + "api_version": "string", + "apps": [ + { + "command": "string", + "display_name": "string", + "external": true, + "health": "disabled", + "healthcheck": { + "interval": 0, + "threshold": 0, + "url": "string" + }, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "sharing_level": "owner", + "slug": "string", + "subdomain": true, + "subdomain_name": "string", + "url": "string" + } + ], + "architecture": "string", + "connection_timeout_seconds": 0, + "created_at": "2019-08-24T14:15:22Z", + "directory": "string", + "disconnected_at": "2019-08-24T14:15:22Z", + "display_apps": ["vscode"], + "environment_variables": { + "property1": "string", + "property2": "string" + }, + "expanded_directory": "string", + "first_connected_at": "2019-08-24T14:15:22Z", + "health": { + "healthy": false, + "reason": "agent has lost connection" + }, + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "instance_id": "string", + "last_connected_at": "2019-08-24T14:15:22Z", + "latency": { + "property1": { + "latency_ms": 0, + "preferred": true + }, + "property2": { + "latency_ms": 0, + "preferred": true + } + }, + "lifecycle_state": "created", + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], + "logs_length": 0, + "logs_overflowed": true, "name": "string", - "username": "string" + "operating_system": "string", + "ready_at": "2019-08-24T14:15:22Z", + "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], + "started_at": "2019-08-24T14:15:22Z", + "startup_script_behavior": "blocking", + "status": "connecting", + "subsystems": ["envbox"], + "troubleshooting_url": "string", + "updated_at": "2019-08-24T14:15:22Z", + "version": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------- | ------ | -------- | ------------ | ----------- | -| `name` | string | false | | | -| `username` | string | true | | | +| Name | Type | Required | Restrictions | Description | +| ---------------------------- | -------------------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `api_version` | string | false | | | +| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | +| `architecture` | string | false | | | +| `connection_timeout_seconds` | integer | false | | | +| `created_at` | string | false | | | +| `directory` | string | false | | | +| `disconnected_at` | string | false | | | +| `display_apps` | array of [codersdk.DisplayApp](#codersdkdisplayapp) | false | | | +| `environment_variables` | object | false | | | +| » `[any property]` | string | false | | | +| `expanded_directory` | string | false | | | +| `first_connected_at` | string | false | | | +| `health` | [codersdk.WorkspaceAgentHealth](#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | +| `id` | string | false | | | +| `instance_id` | string | false | | | +| `last_connected_at` | string | false | | | +| `latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | +| » `[any property]` | [codersdk.DERPRegion](#codersdkderpregion) | false | | | +| `lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | +| `log_sources` | array of [codersdk.WorkspaceAgentLogSource](#codersdkworkspaceagentlogsource) | false | | | +| `logs_length` | integer | false | | | +| `logs_overflowed` | boolean | false | | | +| `name` | string | false | | | +| `operating_system` | string | false | | | +| `ready_at` | string | false | | | +| `resource_id` | string | false | | | +| `scripts` | array of [codersdk.WorkspaceAgentScript](#codersdkworkspaceagentscript) | false | | | +| `started_at` | string | false | | | +| `startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](#codersdkworkspaceagentstartupscriptbehavior) | false | | Startup script behavior is a legacy field that is deprecated in favor of the `coder_script` resource. It's only referenced by old clients. Deprecated: Remove in the future! | +| `status` | [codersdk.WorkspaceAgentStatus](#codersdkworkspaceagentstatus) | false | | | +| `subsystems` | array of [codersdk.AgentSubsystem](#codersdkagentsubsystem) | false | | | +| `troubleshooting_url` | string | false | | | +| `updated_at` | string | false | | | +| `version` | string | false | | | -## codersdk.UpdateUserQuietHoursScheduleRequest +## codersdk.WorkspaceAgentHealth ```json { - "schedule": "string" + "healthy": false, + "reason": "agent has lost connection" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------- | ------ | -------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `schedule` | string | true | | Schedule is a cron expression that defines when the user's quiet hours window is. Schedule must not be empty. For new users, the schedule is set to 2am in their browser or computer's timezone. The schedule denotes the beginning of a 4 hour window where the workspace is allowed to automatically stop or restart due to maintenance or template schedule. | - -The schedule must be daily with a single time, and should have a timezone specified via a CRON_TZ prefix (otherwise UTC will be used). -If the schedule is empty, the user will be updated to use the default schedule.| +| Name | Type | Required | Restrictions | Description | +| --------- | ------- | -------- | ------------ | --------------------------------------------------------------------------------------------- | +| `healthy` | boolean | false | | Healthy is true if the agent is healthy. | +| `reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | -## codersdk.UpdateWorkspaceAutomaticUpdatesRequest +## codersdk.WorkspaceAgentLifecycle ```json -{ - "automatic_updates": "always" -} +"created" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------- | ------------------------------------------------------ | -------- | ------------ | ----------- | -| `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | | +#### Enumerated Values -## codersdk.UpdateWorkspaceAutostartRequest +| Value | +| ------------------ | +| `created` | +| `starting` | +| `start_timeout` | +| `start_error` | +| `ready` | +| `shutting_down` | +| `shutdown_timeout` | +| `shutdown_error` | +| `off` | + +## codersdk.WorkspaceAgentListeningPort ```json { - "schedule": "string" + "network": "string", + "port": 0, + "process_name": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------- | ------ | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `schedule` | string | false | | Schedule is expected to be of the form `CRON_TZ= * * ` Example: `CRON_TZ=US/Central 30 9 * * 1-5` represents 0930 in the timezone US/Central on weekdays (Mon-Fri). `CRON_TZ` defaults to UTC if not present. | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------- | -------- | ------------ | ------------------------ | +| `network` | string | false | | only "tcp" at the moment | +| `port` | integer | false | | | +| `process_name` | string | false | | may be empty | -## codersdk.UpdateWorkspaceDormancy +## codersdk.WorkspaceAgentListeningPortsResponse ```json { - "dormant": true + "ports": [ + { + "network": "string", + "port": 0, + "process_name": "string" + } + ] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------- | -------- | ------------ | ----------- | -| `dormant` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +| ------- | ------------------------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `ports` | array of [codersdk.WorkspaceAgentListeningPort](#codersdkworkspaceagentlisteningport) | false | | 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. | -## codersdk.UpdateWorkspaceRequest +## codersdk.WorkspaceAgentLog ```json { - "name": "string" + "created_at": "2019-08-24T14:15:22Z", + "id": 0, + "level": "trace", + "output": "string", + "source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------ | ------ | -------- | ------------ | ----------- | -| `name` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------ | -------------------------------------- | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `id` | integer | false | | | +| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | +| `output` | string | false | | | +| `source_id` | string | false | | | -## codersdk.UpdateWorkspaceTTLRequest +## codersdk.WorkspaceAgentLogSource ```json { - "ttl_ms": 0 + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | ------- | -------- | ------------ | ----------- | -| `ttl_ms` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------ | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `display_name` | string | false | | | +| `icon` | string | false | | | +| `id` | string | false | | | +| `workspace_agent_id` | string | false | | | -## codersdk.UploadResponse +## codersdk.WorkspaceAgentMetadataDescription ```json { - "hash": "19686d84-b10d-4f90-b18e-84fd3fa038fd" + "display_name": "string", + "interval": 0, + "key": "string", + "script": "string", + "timeout": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------ | ------ | -------- | ------------ | ----------- | -| `hash` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------- | -------- | ------------ | ----------- | +| `display_name` | string | false | | | +| `interval` | integer | false | | | +| `key` | string | false | | | +| `script` | string | false | | | +| `timeout` | integer | false | | | -## codersdk.UpsertWorkspaceAgentPortShareRequest +## codersdk.WorkspaceAgentPortShare ```json { "agent_name": "string", "port": 0, "protocol": "http", - "share_level": "owner" + "share_level": "owner", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | ------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `agent_name` | string | false | | | -| `port` | integer | false | | | -| `protocol` | [codersdk.WorkspaceAgentPortShareProtocol](#codersdkworkspaceagentportshareprotocol) | false | | | -| `share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------- | ------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | +| `agent_name` | string | false | | | +| `port` | integer | false | | | +| `protocol` | [codersdk.WorkspaceAgentPortShareProtocol](#codersdkworkspaceagentportshareprotocol) | false | | | +| `share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | | +| `workspace_id` | string | false | | | #### Enumerated Values @@ -6384,232 +6265,401 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `share_level` | `authenticated` | | `share_level` | `public` | -## codersdk.User +## codersdk.WorkspaceAgentPortShareLevel + +```json +"owner" +``` + +### Properties + +#### Enumerated Values + +| Value | +| --------------- | +| `owner` | +| `authenticated` | +| `public` | + +## codersdk.WorkspaceAgentPortShareProtocol + +```json +"http" +``` + +### Properties + +#### Enumerated Values + +| Value | +| ------- | +| `http` | +| `https` | + +## codersdk.WorkspaceAgentPortShares ```json { - "avatar_url": "http://example.com", - "created_at": "2019-08-24T14:15:22Z", - "email": "user@example.com", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "last_seen_at": "2019-08-24T14:15:22Z", - "login_type": "", - "name": "string", - "organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "roles": [ + "shares": [ { - "display_name": "string", - "name": "string" + "agent_name": "string", + "port": 0, + "protocol": "http", + "share_level": "owner", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" } - ], - "status": "active", - "theme_preference": "string", - "username": "string" + ] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------------------------------------------ | -------- | ------------ | ----------- | -| `avatar_url` | string | false | | | -| `created_at` | string | true | | | -| `email` | string | true | | | -| `id` | string | true | | | -| `last_seen_at` | string | false | | | -| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | | -| `name` | string | false | | | -| `organization_ids` | array of string | false | | | -| `roles` | array of [codersdk.Role](#codersdkrole) | false | | | -| `status` | [codersdk.UserStatus](#codersdkuserstatus) | false | | | -| `theme_preference` | string | false | | | -| `username` | string | true | | | - -#### Enumerated Values - -| Property | Value | -| -------- | ----------- | -| `status` | `active` | -| `status` | `suspended` | +| Name | Type | Required | Restrictions | Description | +| -------- | ----------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `shares` | array of [codersdk.WorkspaceAgentPortShare](#codersdkworkspaceagentportshare) | false | | | -## codersdk.UserActivity +## codersdk.WorkspaceAgentScript ```json { - "avatar_url": "http://example.com", - "seconds": 80500, - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", - "username": "string" + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | --------------- | -------- | ------------ | ----------- | -| `avatar_url` | string | false | | | -| `seconds` | integer | false | | | -| `template_ids` | array of string | false | | | -| `user_id` | string | false | | | -| `username` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------- | -------- | ------------ | ----------- | +| `cron` | string | false | | | +| `log_path` | string | false | | | +| `log_source_id` | string | false | | | +| `run_on_start` | boolean | false | | | +| `run_on_stop` | boolean | false | | | +| `script` | string | false | | | +| `start_blocks_login` | boolean | false | | | +| `timeout` | integer | false | | | + +## codersdk.WorkspaceAgentStartupScriptBehavior + +```json +"blocking" +``` + +### Properties + +#### Enumerated Values + +| Value | +| -------------- | +| `blocking` | +| `non-blocking` | + +## codersdk.WorkspaceAgentStatus + +```json +"connecting" +``` + +### Properties -## codersdk.UserActivityInsightsReport +#### Enumerated Values + +| Value | +| -------------- | +| `connecting` | +| `connected` | +| `disconnected` | +| `timeout` | + +## codersdk.WorkspaceApp ```json { - "end_time": "2019-08-24T14:15:22Z", - "start_time": "2019-08-24T14:15:22Z", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "users": [ - { - "avatar_url": "http://example.com", - "seconds": 80500, - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", - "username": "string" - } - ] + "command": "string", + "display_name": "string", + "external": true, + "health": "disabled", + "healthcheck": { + "interval": 0, + "threshold": 0, + "url": "string" + }, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "sharing_level": "owner", + "slug": "string", + "subdomain": true, + "subdomain_name": "string", + "url": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------------------------------------------------------- | -------- | ------------ | ----------- | -| `end_time` | string | false | | | -| `start_time` | string | false | | | -| `template_ids` | array of string | false | | | -| `users` | array of [codersdk.UserActivity](#codersdkuseractivity) | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------------- | ---------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `command` | string | false | | | +| `display_name` | string | false | | Display name is a friendly name for the app. | +| `external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. | +| `health` | [codersdk.WorkspaceAppHealth](#codersdkworkspaceapphealth) | false | | | +| `healthcheck` | [codersdk.Healthcheck](#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. | +| `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. | +| `id` | string | false | | | +| `sharing_level` | [codersdk.WorkspaceAppSharingLevel](#codersdkworkspaceappsharinglevel) | false | | | +| `slug` | string | false | | Slug is a unique identifier within the agent. | +| `subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | +| `subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | +| `url` | string | false | | URL is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | -## codersdk.UserActivityInsightsResponse +#### Enumerated Values + +| Property | Value | +| --------------- | --------------- | +| `sharing_level` | `owner` | +| `sharing_level` | `authenticated` | +| `sharing_level` | `public` | + +## codersdk.WorkspaceAppHealth ```json -{ - "report": { - "end_time": "2019-08-24T14:15:22Z", - "start_time": "2019-08-24T14:15:22Z", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "users": [ - { - "avatar_url": "http://example.com", - "seconds": 80500, - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", - "username": "string" - } - ] - } -} +"disabled" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | -------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `report` | [codersdk.UserActivityInsightsReport](#codersdkuseractivityinsightsreport) | false | | | +#### Enumerated Values -## codersdk.UserLatency +| Value | +| -------------- | +| `disabled` | +| `initializing` | +| `healthy` | +| `unhealthy` | + +## codersdk.WorkspaceAppSharingLevel ```json -{ - "avatar_url": "http://example.com", - "latency_ms": { - "p50": 31.312, - "p95": 119.832 - }, - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", - "username": "string" -} +"owner" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | -------------------------------------------------------- | -------- | ------------ | ----------- | -| `avatar_url` | string | false | | | -| `latency_ms` | [codersdk.ConnectionLatency](#codersdkconnectionlatency) | false | | | -| `template_ids` | array of string | false | | | -| `user_id` | string | false | | | -| `username` | string | false | | | +#### Enumerated Values -## codersdk.UserLatencyInsightsReport +| Value | +| --------------- | +| `owner` | +| `authenticated` | +| `public` | + +## codersdk.WorkspaceBuild ```json { - "end_time": "2019-08-24T14:15:22Z", - "start_time": "2019-08-24T14:15:22Z", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "users": [ + "build_number": 0, + "created_at": "2019-08-24T14:15:22Z", + "daily_cost": 0, + "deadline": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", + "initiator_name": "string", + "job": { + "canceled_at": "2019-08-24T14:15:22Z", + "completed_at": "2019-08-24T14:15:22Z", + "created_at": "2019-08-24T14:15:22Z", + "error": "string", + "error_code": "REQUIRED_TEMPLATE_VARIABLES", + "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, + "started_at": "2019-08-24T14:15:22Z", + "status": "pending", + "tags": { + "property1": "string", + "property2": "string" + }, + "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" + }, + "max_deadline": "2019-08-24T14:15:22Z", + "reason": "initiator", + "resources": [ { - "avatar_url": "http://example.com", - "latency_ms": { - "p50": 31.312, - "p95": 119.832 - }, - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", - "username": "string" + "agents": [ + { + "api_version": "string", + "apps": [ + { + "command": "string", + "display_name": "string", + "external": true, + "health": "disabled", + "healthcheck": { + "interval": 0, + "threshold": 0, + "url": "string" + }, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "sharing_level": "owner", + "slug": "string", + "subdomain": true, + "subdomain_name": "string", + "url": "string" + } + ], + "architecture": "string", + "connection_timeout_seconds": 0, + "created_at": "2019-08-24T14:15:22Z", + "directory": "string", + "disconnected_at": "2019-08-24T14:15:22Z", + "display_apps": ["vscode"], + "environment_variables": { + "property1": "string", + "property2": "string" + }, + "expanded_directory": "string", + "first_connected_at": "2019-08-24T14:15:22Z", + "health": { + "healthy": false, + "reason": "agent has lost connection" + }, + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "instance_id": "string", + "last_connected_at": "2019-08-24T14:15:22Z", + "latency": { + "property1": { + "latency_ms": 0, + "preferred": true + }, + "property2": { + "latency_ms": 0, + "preferred": true + } + }, + "lifecycle_state": "created", + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], + "logs_length": 0, + "logs_overflowed": true, + "name": "string", + "operating_system": "string", + "ready_at": "2019-08-24T14:15:22Z", + "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], + "started_at": "2019-08-24T14:15:22Z", + "startup_script_behavior": "blocking", + "status": "connecting", + "subsystems": ["envbox"], + "troubleshooting_url": "string", + "updated_at": "2019-08-24T14:15:22Z", + "version": "string" + } + ], + "created_at": "2019-08-24T14:15:22Z", + "daily_cost": 0, + "hide": true, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", + "metadata": [ + { + "key": "string", + "sensitive": true, + "value": "string" + } + ], + "name": "string", + "type": "string", + "workspace_transition": "start" } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------- | ----------------------------------------------------- | -------- | ------------ | ----------- | -| `end_time` | string | false | | | -| `start_time` | string | false | | | -| `template_ids` | array of string | false | | | -| `users` | array of [codersdk.UserLatency](#codersdkuserlatency) | false | | | - -## codersdk.UserLatencyInsightsResponse - -```json -{ - "report": { - "end_time": "2019-08-24T14:15:22Z", - "start_time": "2019-08-24T14:15:22Z", - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "users": [ - { - "avatar_url": "http://example.com", - "latency_ms": { - "p50": 31.312, - "p95": 119.832 - }, - "template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "user_id": "a169451c-8525-4352-b8ca-070dd449a1a5", - "username": "string" - } - ] - } + ], + "status": "pending", + "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", + "template_version_name": "string", + "transition": "start", + "updated_at": "2019-08-24T14:15:22Z", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9", + "workspace_name": "string", + "workspace_owner_avatar_url": "string", + "workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7", + "workspace_owner_name": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | ------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `report` | [codersdk.UserLatencyInsightsReport](#codersdkuserlatencyinsightsreport) | false | | | - -## codersdk.UserLoginType - -```json -{ - "login_type": "" -} -``` +| Name | Type | Required | Restrictions | Description | +| ---------------------------- | ----------------------------------------------------------------- | -------- | ------------ | ----------- | +| `build_number` | integer | false | | | +| `created_at` | string | false | | | +| `daily_cost` | integer | false | | | +| `deadline` | string | false | | | +| `id` | string | false | | | +| `initiator_id` | string | false | | | +| `initiator_name` | string | false | | | +| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | | +| `max_deadline` | string | false | | | +| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | | +| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | | +| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | | +| `template_version_id` | string | false | | | +| `template_version_name` | string | false | | | +| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | | +| `updated_at` | string | false | | | +| `workspace_id` | string | false | | | +| `workspace_name` | string | false | | | +| `workspace_owner_avatar_url` | string | false | | | +| `workspace_owner_id` | string | false | | | +| `workspace_owner_name` | string | false | | | -### Properties +#### Enumerated Values -| Name | Type | Required | Restrictions | Description | -| ------------ | ---------------------------------------- | -------- | ------------ | ----------- | -| `login_type` | [codersdk.LoginType](#codersdklogintype) | false | | | +| Property | Value | +| ------------ | ----------- | +| `reason` | `initiator` | +| `reason` | `autostart` | +| `reason` | `autostop` | +| `status` | `pending` | +| `status` | `starting` | +| `status` | `running` | +| `status` | `stopping` | +| `status` | `stopped` | +| `status` | `failed` | +| `status` | `canceling` | +| `status` | `canceled` | +| `status` | `deleting` | +| `status` | `deleted` | +| `transition` | `start` | +| `transition` | `stop` | +| `transition` | `delete` | -## codersdk.UserParameter +## codersdk.WorkspaceBuildParameter ```json { @@ -6625,636 +6675,525 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `name` | string | false | | | | `value` | string | false | | | -## codersdk.UserQuietHoursScheduleConfig - -```json -{ - "allow_user_custom": true, - "default_schedule": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------- | ------- | -------- | ------------ | ----------- | -| `allow_user_custom` | boolean | false | | | -| `default_schedule` | string | false | | | - -## codersdk.UserQuietHoursScheduleResponse +## codersdk.WorkspaceConnectionLatencyMS ```json { - "next": "2019-08-24T14:15:22Z", - "raw_schedule": "string", - "time": "string", - "timezone": "string", - "user_can_set": true, - "user_set": true + "p50": 0, + "p95": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------- | -------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `next` | string | false | | Next is the next time that the quiet hours window will start. | -| `raw_schedule` | string | false | | | -| `time` | string | false | | Time is the time of day that the quiet hours window starts in the given Timezone each day. | -| `timezone` | string | false | | raw format from the cron expression, UTC if unspecified | -| `user_can_set` | boolean | false | | User can set is true if the user is allowed to set their own quiet hours schedule. If false, the user cannot set a custom schedule and the default schedule will always be used. | -| `user_set` | boolean | false | | User set is true if the user has set their own quiet hours schedule. If false, the user is using the default schedule. | - -## codersdk.UserStatus - -```json -"active" -``` - -### Properties - -#### Enumerated Values - -| Value | -| ----------- | -| `active` | -| `dormant` | -| `suspended` | +| Name | Type | Required | Restrictions | Description | +| ----- | ------ | -------- | ------------ | ----------- | +| `p50` | number | false | | | +| `p95` | number | false | | | -## codersdk.ValidationError +## codersdk.WorkspaceDeploymentStats ```json { - "detail": "string", - "field": "string" + "building": 0, + "connection_latency_ms": { + "p50": 0, + "p95": 0 + }, + "failed": 0, + "pending": 0, + "running": 0, + "rx_bytes": 0, + "stopped": 0, + "tx_bytes": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | ------ | -------- | ------------ | ----------- | -| `detail` | string | true | | | -| `field` | string | true | | | - -## codersdk.ValidationMonotonicOrder - -```json -"increasing" -``` - -### Properties - -#### Enumerated Values - -| Value | -| ------------ | -| `increasing` | -| `decreasing` | +| Name | Type | Required | Restrictions | Description | +| ----------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------- | +| `building` | integer | false | | | +| `connection_latency_ms` | [codersdk.WorkspaceConnectionLatencyMS](#codersdkworkspaceconnectionlatencyms) | false | | | +| `failed` | integer | false | | | +| `pending` | integer | false | | | +| `running` | integer | false | | | +| `rx_bytes` | integer | false | | | +| `stopped` | integer | false | | | +| `tx_bytes` | integer | false | | | -## codersdk.VariableValue +## codersdk.WorkspaceHealth ```json { - "name": "string", - "value": "string" + "failing_agents": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "healthy": false } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------- | ------ | -------- | ------------ | ----------- | -| `name` | string | false | | | -| `value` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------------- | --------------- | -------- | ------------ | -------------------------------------------------------------------- | +| `failing_agents` | array of string | false | | Failing agents lists the IDs of the agents that are failing, if any. | +| `healthy` | boolean | false | | Healthy is true if the workspace is healthy. | -## codersdk.WebsocketReport +## codersdk.WorkspaceProxy ```json { - "body": "string", - "code": 0, - "dismissed": true, - "error": "string", + "created_at": "2019-08-24T14:15:22Z", + "deleted": true, + "derp_enabled": true, + "derp_only": true, + "display_name": "string", "healthy": true, - "severity": "ok", - "warnings": ["string"] + "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 | -| ----------- | ---------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------- | -| `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` | +| Name | Type | Required | Restrictions | Description | +| ------------------- | -------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `created_at` | string | false | | | +| `deleted` | boolean | false | | | +| `derp_enabled` | boolean | false | | | +| `derp_only` | boolean | false | | | +| `display_name` | string | false | | | +| `healthy` | boolean | false | | | +| `icon_url` | string | false | | | +| `id` | string | false | | | +| `name` | string | false | | | +| `path_app_url` | string | false | | Path app URL is the URL to the base path for path apps. Optional unless wildcard_hostname is set. E.g. https://us.example.com | +| `status` | [codersdk.WorkspaceProxyStatus](#codersdkworkspaceproxystatus) | false | | Status is the latest status check of the proxy. This will be empty for deleted proxies. This value can be used to determine if a workspace proxy is healthy and ready to use. | +| `updated_at` | string | false | | | +| `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.Workspace +## codersdk.WorkspaceProxyStatus ```json { - "allow_renames": true, - "automatic_updates": "always", - "autostart_schedule": "string", - "created_at": "2019-08-24T14:15:22Z", - "deleting_at": "2019-08-24T14:15:22Z", - "dormant_at": "2019-08-24T14:15:22Z", - "favorite": true, - "health": { - "failing_agents": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "healthy": false - }, - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "last_used_at": "2019-08-24T14:15:22Z", - "latest_build": { - "build_number": 0, - "created_at": "2019-08-24T14:15:22Z", - "daily_cost": 0, - "deadline": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", - "initiator_name": "string", - "job": { - "canceled_at": "2019-08-24T14:15:22Z", - "completed_at": "2019-08-24T14:15:22Z", - "created_at": "2019-08-24T14:15:22Z", - "error": "string", - "error_code": "REQUIRED_TEMPLATE_VARIABLES", - "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "queue_position": 0, - "queue_size": 0, - "started_at": "2019-08-24T14:15:22Z", - "status": "pending", - "tags": { - "property1": "string", - "property2": "string" - }, - "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" - }, - "max_deadline": "2019-08-24T14:15:22Z", - "reason": "initiator", - "resources": [ - { - "agents": [ - { - "api_version": "string", - "apps": [ - { - "command": "string", - "display_name": "string", - "external": true, - "health": "disabled", - "healthcheck": { - "interval": 0, - "threshold": 0, - "url": "string" - }, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "sharing_level": "owner", - "slug": "string", - "subdomain": true, - "subdomain_name": "string", - "url": "string" - } - ], - "architecture": "string", - "connection_timeout_seconds": 0, - "created_at": "2019-08-24T14:15:22Z", - "directory": "string", - "disconnected_at": "2019-08-24T14:15:22Z", - "display_apps": ["vscode"], - "environment_variables": { - "property1": "string", - "property2": "string" - }, - "expanded_directory": "string", - "first_connected_at": "2019-08-24T14:15:22Z", - "health": { - "healthy": false, - "reason": "agent has lost connection" - }, - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "instance_id": "string", - "last_connected_at": "2019-08-24T14:15:22Z", - "latency": { - "property1": { - "latency_ms": 0, - "preferred": true - }, - "property2": { - "latency_ms": 0, - "preferred": true - } - }, - "lifecycle_state": "created", - "log_sources": [ - { - "created_at": "2019-08-24T14:15:22Z", - "display_name": "string", - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" - } - ], - "logs_length": 0, - "logs_overflowed": true, - "name": "string", - "operating_system": "string", - "ready_at": "2019-08-24T14:15:22Z", - "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "scripts": [ - { - "cron": "string", - "log_path": "string", - "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", - "run_on_start": true, - "run_on_stop": true, - "script": "string", - "start_blocks_login": true, - "timeout": 0 - } - ], - "started_at": "2019-08-24T14:15:22Z", - "startup_script_behavior": "blocking", - "status": "connecting", - "subsystems": ["envbox"], - "troubleshooting_url": "string", - "updated_at": "2019-08-24T14:15:22Z", - "version": "string" - } - ], - "created_at": "2019-08-24T14:15:22Z", - "daily_cost": 0, - "hide": true, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", - "metadata": [ - { - "key": "string", - "sensitive": true, - "value": "string" - } - ], - "name": "string", - "type": "string", - "workspace_transition": "start" - } - ], - "status": "pending", - "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", - "template_version_name": "string", - "transition": "start", - "updated_at": "2019-08-24T14:15:22Z", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9", - "workspace_name": "string", - "workspace_owner_avatar_url": "string", - "workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7", - "workspace_owner_name": "string" + "checked_at": "2019-08-24T14:15:22Z", + "report": { + "errors": ["string"], + "warnings": ["string"] }, - "name": "string", - "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", - "outdated": true, - "owner_avatar_url": "string", - "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", - "owner_name": "string", - "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", - "template_allow_user_cancel_workspace_jobs": true, - "template_display_name": "string", - "template_icon": "string", - "template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc", - "template_name": "string", - "template_require_active_version": true, - "ttl_ms": 0, - "updated_at": "2019-08-24T14:15:22Z" + "status": "ok" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------------------------------- | ------------------------------------------------------ | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `allow_renames` | boolean | false | | | -| `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | | -| `autostart_schedule` | string | false | | | -| `created_at` | string | false | | | -| `deleting_at` | string | false | | Deleting at indicates the time at which the workspace will be permanently deleted. A workspace is eligible for deletion if it is dormant (a non-nil dormant_at value) and a value has been specified for time_til_dormant_autodelete on its template. | -| `dormant_at` | string | false | | Dormant at being non-nil indicates a workspace that is dormant. A dormant workspace is no longer accessible must be activated. It is subject to deletion if it breaches the duration of the time*til* field on its template. | -| `favorite` | boolean | false | | | -| `health` | [codersdk.WorkspaceHealth](#codersdkworkspacehealth) | false | | Health shows the health of the workspace and information about what is causing an unhealthy status. | -| `id` | string | false | | | -| `last_used_at` | string | false | | | -| `latest_build` | [codersdk.WorkspaceBuild](#codersdkworkspacebuild) | false | | | -| `name` | string | false | | | -| `organization_id` | string | false | | | -| `outdated` | boolean | false | | | -| `owner_avatar_url` | string | false | | | -| `owner_id` | string | false | | | -| `owner_name` | string | false | | | -| `template_active_version_id` | string | false | | | -| `template_allow_user_cancel_workspace_jobs` | boolean | false | | | -| `template_display_name` | string | false | | | -| `template_icon` | string | false | | | -| `template_id` | string | false | | | -| `template_name` | string | false | | | -| `template_require_active_version` | boolean | false | | | -| `ttl_ms` | integer | false | | | -| `updated_at` | string | false | | | - -#### Enumerated Values - -| Property | Value | -| ------------------- | -------- | -| `automatic_updates` | `always` | -| `automatic_updates` | `never` | +| Name | Type | Required | Restrictions | Description | +| ------------ | -------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------- | +| `checked_at` | string | false | | | +| `report` | [codersdk.ProxyHealthReport](#codersdkproxyhealthreport) | false | | Report provides more information about the health of the workspace proxy. | +| `status` | [codersdk.ProxyHealthStatus](#codersdkproxyhealthstatus) | false | | | -## codersdk.WorkspaceAgent +## codersdk.WorkspaceQuota ```json { - "api_version": "string", - "apps": [ - { - "command": "string", - "display_name": "string", - "external": true, - "health": "disabled", - "healthcheck": { - "interval": 0, - "threshold": 0, - "url": "string" - }, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "sharing_level": "owner", - "slug": "string", - "subdomain": true, - "subdomain_name": "string", - "url": "string" - } - ], - "architecture": "string", - "connection_timeout_seconds": 0, - "created_at": "2019-08-24T14:15:22Z", - "directory": "string", - "disconnected_at": "2019-08-24T14:15:22Z", - "display_apps": ["vscode"], - "environment_variables": { - "property1": "string", - "property2": "string" - }, - "expanded_directory": "string", - "first_connected_at": "2019-08-24T14:15:22Z", - "health": { - "healthy": false, - "reason": "agent has lost connection" - }, - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "instance_id": "string", - "last_connected_at": "2019-08-24T14:15:22Z", - "latency": { - "property1": { - "latency_ms": 0, - "preferred": true - }, - "property2": { - "latency_ms": 0, - "preferred": true - } - }, - "lifecycle_state": "created", - "log_sources": [ - { - "created_at": "2019-08-24T14:15:22Z", - "display_name": "string", - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" - } - ], - "logs_length": 0, - "logs_overflowed": true, - "name": "string", - "operating_system": "string", - "ready_at": "2019-08-24T14:15:22Z", - "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "scripts": [ - { - "cron": "string", - "log_path": "string", - "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", - "run_on_start": true, - "run_on_stop": true, - "script": "string", - "start_blocks_login": true, - "timeout": 0 - } - ], - "started_at": "2019-08-24T14:15:22Z", - "startup_script_behavior": "blocking", - "status": "connecting", - "subsystems": ["envbox"], - "troubleshooting_url": "string", - "updated_at": "2019-08-24T14:15:22Z", - "version": "string" + "budget": 0, + "credits_consumed": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------------------- | -------------------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `api_version` | string | false | | | -| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | -| `architecture` | string | false | | | -| `connection_timeout_seconds` | integer | false | | | -| `created_at` | string | false | | | -| `directory` | string | false | | | -| `disconnected_at` | string | false | | | -| `display_apps` | array of [codersdk.DisplayApp](#codersdkdisplayapp) | false | | | -| `environment_variables` | object | false | | | -| » `[any property]` | string | false | | | -| `expanded_directory` | string | false | | | -| `first_connected_at` | string | false | | | -| `health` | [codersdk.WorkspaceAgentHealth](#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | -| `id` | string | false | | | -| `instance_id` | string | false | | | -| `last_connected_at` | string | false | | | -| `latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | -| » `[any property]` | [codersdk.DERPRegion](#codersdkderpregion) | false | | | -| `lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | -| `log_sources` | array of [codersdk.WorkspaceAgentLogSource](#codersdkworkspaceagentlogsource) | false | | | -| `logs_length` | integer | false | | | -| `logs_overflowed` | boolean | false | | | -| `name` | string | false | | | -| `operating_system` | string | false | | | -| `ready_at` | string | false | | | -| `resource_id` | string | false | | | -| `scripts` | array of [codersdk.WorkspaceAgentScript](#codersdkworkspaceagentscript) | false | | | -| `started_at` | string | false | | | -| `startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](#codersdkworkspaceagentstartupscriptbehavior) | false | | Startup script behavior is a legacy field that is deprecated in favor of the `coder_script` resource. It's only referenced by old clients. Deprecated: Remove in the future! | -| `status` | [codersdk.WorkspaceAgentStatus](#codersdkworkspaceagentstatus) | false | | | -| `subsystems` | array of [codersdk.AgentSubsystem](#codersdkagentsubsystem) | false | | | -| `troubleshooting_url` | string | false | | | -| `updated_at` | string | false | | | -| `version` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------- | -------- | ------------ | ----------- | +| `budget` | integer | false | | | +| `credits_consumed` | integer | false | | | -## codersdk.WorkspaceAgentConnectionInfo +## codersdk.WorkspaceResource ```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" + "agents": [ + { + "api_version": "string", + "apps": [ + { + "command": "string", + "display_name": "string", + "external": true, + "health": "disabled", + "healthcheck": { + "interval": 0, + "threshold": 0, + "url": "string" + }, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "sharing_level": "owner", + "slug": "string", + "subdomain": true, + "subdomain_name": "string", + "url": "string" + } + ], + "architecture": "string", + "connection_timeout_seconds": 0, + "created_at": "2019-08-24T14:15:22Z", + "directory": "string", + "disconnected_at": "2019-08-24T14:15:22Z", + "display_apps": ["vscode"], + "environment_variables": { + "property1": "string", + "property2": "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" - } + "expanded_directory": "string", + "first_connected_at": "2019-08-24T14:15:22Z", + "health": { + "healthy": false, + "reason": "agent has lost connection" + }, + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "instance_id": "string", + "last_connected_at": "2019-08-24T14:15:22Z", + "latency": { + "property1": { + "latency_ms": 0, + "preferred": true + }, + "property2": { + "latency_ms": 0, + "preferred": true + } + }, + "lifecycle_state": "created", + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], + "logs_length": 0, + "logs_overflowed": true, + "name": "string", + "operating_system": "string", + "ready_at": "2019-08-24T14:15:22Z", + "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], + "started_at": "2019-08-24T14:15:22Z", + "startup_script_behavior": "blocking", + "status": "connecting", + "subsystems": ["envbox"], + "troubleshooting_url": "string", + "updated_at": "2019-08-24T14:15:22Z", + "version": "string" } - }, - "disable_direct_connections": true + ], + "created_at": "2019-08-24T14:15:22Z", + "daily_cost": 0, + "hide": true, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", + "metadata": [ + { + "key": "string", + "sensitive": true, + "value": "string" + } + ], + "name": "string", + "type": "string", + "workspace_transition": "start" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------------------- | ---------------------------------- | -------- | ------------ | ----------- | -| `derp_force_websockets` | boolean | false | | | -| `derp_map` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | | -| `disable_direct_connections` | boolean | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------------------- | --------------------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `agents` | array of [codersdk.WorkspaceAgent](#codersdkworkspaceagent) | false | | | +| `created_at` | string | false | | | +| `daily_cost` | integer | false | | | +| `hide` | boolean | false | | | +| `icon` | string | false | | | +| `id` | string | false | | | +| `job_id` | string | false | | | +| `metadata` | array of [codersdk.WorkspaceResourceMetadata](#codersdkworkspaceresourcemetadata) | false | | | +| `name` | string | false | | | +| `type` | string | false | | | +| `workspace_transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | | -## codersdk.WorkspaceAgentHealth +#### Enumerated Values + +| Property | Value | +| ---------------------- | -------- | +| `workspace_transition` | `start` | +| `workspace_transition` | `stop` | +| `workspace_transition` | `delete` | + +## codersdk.WorkspaceResourceMetadata ```json { - "healthy": false, - "reason": "agent has lost connection" + "key": "string", + "sensitive": true, + "value": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------- | -------- | ------------ | --------------------------------------------------------------------------------------------- | -| `healthy` | boolean | false | | Healthy is true if the agent is healthy. | -| `reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | +| Name | Type | Required | Restrictions | Description | +| ----------- | ------- | -------- | ------------ | ----------- | +| `key` | string | false | | | +| `sensitive` | boolean | false | | | +| `value` | string | false | | | -## codersdk.WorkspaceAgentLifecycle +## codersdk.WorkspaceStatus ```json -"created" +"pending" ``` ### Properties #### Enumerated Values -| Value | -| ------------------ | -| `created` | -| `starting` | -| `start_timeout` | -| `start_error` | -| `ready` | -| `shutting_down` | -| `shutdown_timeout` | -| `shutdown_error` | -| `off` | +| Value | +| ----------- | +| `pending` | +| `starting` | +| `running` | +| `stopping` | +| `stopped` | +| `failed` | +| `canceling` | +| `canceled` | +| `deleting` | +| `deleted` | -## codersdk.WorkspaceAgentListeningPort +## codersdk.WorkspaceTransition ```json -{ - "network": "string", - "port": 0, - "process_name": "string" -} +"start" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------- | -------- | ------------ | ------------------------ | -| `network` | string | false | | only "tcp" at the moment | -| `port` | integer | false | | | -| `process_name` | string | false | | may be empty | +#### Enumerated Values -## codersdk.WorkspaceAgentListeningPortsResponse +| Value | +| -------- | +| `start` | +| `stop` | +| `delete` | + +## codersdk.WorkspacesResponse ```json { - "ports": [ + "count": 0, + "workspaces": [ { - "network": "string", - "port": 0, - "process_name": "string" + "allow_renames": true, + "automatic_updates": "always", + "autostart_schedule": "string", + "created_at": "2019-08-24T14:15:22Z", + "deleting_at": "2019-08-24T14:15:22Z", + "dormant_at": "2019-08-24T14:15:22Z", + "favorite": true, + "health": { + "failing_agents": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], + "healthy": false + }, + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "last_used_at": "2019-08-24T14:15:22Z", + "latest_build": { + "build_number": 0, + "created_at": "2019-08-24T14:15:22Z", + "daily_cost": 0, + "deadline": "2019-08-24T14:15:22Z", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", + "initiator_name": "string", + "job": { + "canceled_at": "2019-08-24T14:15:22Z", + "completed_at": "2019-08-24T14:15:22Z", + "created_at": "2019-08-24T14:15:22Z", + "error": "string", + "error_code": "REQUIRED_TEMPLATE_VARIABLES", + "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "queue_position": 0, + "queue_size": 0, + "started_at": "2019-08-24T14:15:22Z", + "status": "pending", + "tags": { + "property1": "string", + "property2": "string" + }, + "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" + }, + "max_deadline": "2019-08-24T14:15:22Z", + "reason": "initiator", + "resources": [ + { + "agents": [ + { + "api_version": "string", + "apps": [ + { + "command": "string", + "display_name": "string", + "external": true, + "health": "disabled", + "healthcheck": {}, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "sharing_level": "owner", + "slug": "string", + "subdomain": true, + "subdomain_name": "string", + "url": "string" + } + ], + "architecture": "string", + "connection_timeout_seconds": 0, + "created_at": "2019-08-24T14:15:22Z", + "directory": "string", + "disconnected_at": "2019-08-24T14:15:22Z", + "display_apps": ["vscode"], + "environment_variables": { + "property1": "string", + "property2": "string" + }, + "expanded_directory": "string", + "first_connected_at": "2019-08-24T14:15:22Z", + "health": { + "healthy": false, + "reason": "agent has lost connection" + }, + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "instance_id": "string", + "last_connected_at": "2019-08-24T14:15:22Z", + "latency": { + "property1": { + "latency_ms": 0, + "preferred": true + }, + "property2": { + "latency_ms": 0, + "preferred": true + } + }, + "lifecycle_state": "created", + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], + "logs_length": 0, + "logs_overflowed": true, + "name": "string", + "operating_system": "string", + "ready_at": "2019-08-24T14:15:22Z", + "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], + "started_at": "2019-08-24T14:15:22Z", + "startup_script_behavior": "blocking", + "status": "connecting", + "subsystems": ["envbox"], + "troubleshooting_url": "string", + "updated_at": "2019-08-24T14:15:22Z", + "version": "string" + } + ], + "created_at": "2019-08-24T14:15:22Z", + "daily_cost": 0, + "hide": true, + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", + "metadata": [ + { + "key": "string", + "sensitive": true, + "value": "string" + } + ], + "name": "string", + "type": "string", + "workspace_transition": "start" + } + ], + "status": "pending", + "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", + "template_version_name": "string", + "transition": "start", + "updated_at": "2019-08-24T14:15:22Z", + "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9", + "workspace_name": "string", + "workspace_owner_avatar_url": "string", + "workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7", + "workspace_owner_name": "string" + }, + "name": "string", + "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "outdated": true, + "owner_avatar_url": "string", + "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", + "owner_name": "string", + "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", + "template_allow_user_cancel_workspace_jobs": true, + "template_display_name": "string", + "template_icon": "string", + "template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc", + "template_name": "string", + "template_require_active_version": true, + "ttl_ms": 0, + "updated_at": "2019-08-24T14:15:22Z" } ] } @@ -7262,1092 +7201,1019 @@ If the schedule is empty, the user will be updated to use the default schedule.| ### Properties -| Name | Type | Required | Restrictions | Description | -| ------- | ------------------------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `ports` | array of [codersdk.WorkspaceAgentListeningPort](#codersdkworkspaceagentlisteningport) | false | | 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. | - -## codersdk.WorkspaceAgentLog - -```json -{ - "created_at": "2019-08-24T14:15:22Z", - "id": 0, - "level": "trace", - "output": "string", - "source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | -------------------------------------- | -------- | ------------ | ----------- | -| `created_at` | string | false | | | -| `id` | integer | false | | | -| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | -| `output` | string | false | | | -| `source_id` | string | false | | | - -## codersdk.WorkspaceAgentLogSource - -```json -{ - "created_at": "2019-08-24T14:15:22Z", - "display_name": "string", - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------------------- | ------ | -------- | ------------ | ----------- | -| `created_at` | string | false | | | -| `display_name` | string | false | | | -| `icon` | string | false | | | -| `id` | string | false | | | -| `workspace_agent_id` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------ | ------------------------------------------------- | -------- | ------------ | ----------- | +| `count` | integer | false | | | +| `workspaces` | array of [codersdk.Workspace](#codersdkworkspace) | false | | | -## codersdk.WorkspaceAgentMetadataDescription +## derp.BytesSentRecv ```json { - "display_name": "string", - "interval": 0, - "key": "string", - "script": "string", - "timeout": 0 + "key": {}, + "recv": 0, + "sent": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------- | -------- | ------------ | ----------- | -| `display_name` | string | false | | | -| `interval` | integer | false | | | -| `key` | string | false | | | -| `script` | string | false | | | -| `timeout` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| ------ | -------------------------------- | -------- | ------------ | -------------------------------------------------------------------- | +| `key` | [key.NodePublic](#keynodepublic) | false | | Key is the public key of the client which sent/received these bytes. | +| `recv` | integer | false | | | +| `sent` | integer | false | | | -## codersdk.WorkspaceAgentPortShare +## derp.ServerInfoMessage ```json { - "agent_name": "string", - "port": 0, - "protocol": "http", - "share_level": "owner", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" + "tokenBucketBytesBurst": 0, + "tokenBucketBytesPerSecond": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------- | ------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `agent_name` | string | false | | | -| `port` | integer | false | | | -| `protocol` | [codersdk.WorkspaceAgentPortShareProtocol](#codersdkworkspaceagentportshareprotocol) | false | | | -| `share_level` | [codersdk.WorkspaceAgentPortShareLevel](#codersdkworkspaceagentportsharelevel) | false | | | -| `workspace_id` | string | false | | | - -#### Enumerated Values - -| Property | Value | -| ------------- | --------------- | -| `protocol` | `http` | -| `protocol` | `https` | -| `share_level` | `owner` | -| `share_level` | `authenticated` | -| `share_level` | `public` | - -## codersdk.WorkspaceAgentPortShareLevel - -```json -"owner" -``` - -### Properties - -#### Enumerated Values - -| Value | -| --------------- | -| `owner` | -| `authenticated` | -| `public` | +| Name | Type | Required | Restrictions | Description | +| ------------------------------------------------------------------------------------------ | ------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------ | +| `tokenBucketBytesBurst` | integer | false | | Tokenbucketbytesburst is how many bytes the server will allow to burst, temporarily violating TokenBucketBytesPerSecond. | +| Zero means unspecified. There might be a limit, but the client need not try to respect it. | +| `tokenBucketBytesPerSecond` | integer | false | | Tokenbucketbytespersecond is how many bytes per second the server says it will accept, including all framing bytes. | +| Zero means unspecified. There might be a limit, but the client need not try to respect it. | -## codersdk.WorkspaceAgentPortShareProtocol +## health.Code ```json -"http" +"EUNKNOWN" ``` ### Properties #### Enumerated Values -| Value | -| ------- | -| `http` | -| `https` | - -## codersdk.WorkspaceAgentPortShares - -```json -{ - "shares": [ - { - "agent_name": "string", - "port": 0, - "protocol": "http", - "share_level": "owner", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| -------- | ----------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `shares` | array of [codersdk.WorkspaceAgentPortShare](#codersdkworkspaceagentportshare) | false | | | +| Value | +| ---------- | +| `EUNKNOWN` | +| `EWP01` | +| `EWP02` | +| `EWP04` | +| `EDB01` | +| `EDB02` | +| `EWS01` | +| `EWS02` | +| `EWS03` | +| `EACS01` | +| `EACS02` | +| `EACS03` | +| `EACS04` | +| `EDERP01` | +| `EDERP02` | +| `EPD01` | +| `EPD02` | +| `EPD03` | -## codersdk.WorkspaceAgentScript +## health.Message ```json { - "cron": "string", - "log_path": "string", - "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", - "run_on_start": true, - "run_on_stop": true, - "script": "string", - "start_blocks_login": true, - "timeout": 0 + "code": "EUNKNOWN", + "message": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------------------- | ------- | -------- | ------------ | ----------- | -| `cron` | string | false | | | -| `log_path` | string | false | | | -| `log_source_id` | string | false | | | -| `run_on_start` | boolean | false | | | -| `run_on_stop` | boolean | false | | | -| `script` | string | false | | | -| `start_blocks_login` | boolean | false | | | -| `timeout` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| --------- | -------------------------- | -------- | ------------ | ----------- | +| `code` | [health.Code](#healthcode) | false | | | +| `message` | string | false | | | -## codersdk.WorkspaceAgentStartupScriptBehavior +## health.Severity ```json -"blocking" +"ok" ``` ### Properties #### Enumerated Values -| Value | -| -------------- | -| `blocking` | -| `non-blocking` | +| Value | +| --------- | +| `ok` | +| `warning` | +| `error` | -## codersdk.WorkspaceAgentStatus +## healthsdk.AccessURLReport ```json -"connecting" +{ + "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 -| Value | -| -------------- | -| `connecting` | -| `connected` | -| `disconnected` | -| `timeout` | +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | -## codersdk.WorkspaceApp +## healthsdk.DERPHealthReport ```json { - "command": "string", - "display_name": "string", - "external": true, - "health": "disabled", - "healthcheck": { - "interval": 0, - "threshold": 0, - "url": "string" + "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" }, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "sharing_level": "owner", - "slug": "string", - "subdomain": true, - "subdomain_name": "string", - "url": "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 | -| ---------------- | ---------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `command` | string | false | | | -| `display_name` | string | false | | Display name is a friendly name for the app. | -| `external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. | -| `health` | [codersdk.WorkspaceAppHealth](#codersdkworkspaceapphealth) | false | | | -| `healthcheck` | [codersdk.Healthcheck](#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. | -| `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. | -| `id` | string | false | | | -| `sharing_level` | [codersdk.WorkspaceAppSharingLevel](#codersdkworkspaceappsharinglevel) | false | | | -| `slug` | string | false | | Slug is a unique identifier within the agent. | -| `subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | -| `subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | -| `url` | string | false | | URL is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | +| 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 | -| --------------- | --------------- | -| `sharing_level` | `owner` | -| `sharing_level` | `authenticated` | -| `sharing_level` | `public` | +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | -## codersdk.WorkspaceAppHealth +## healthsdk.DERPNodeReport ```json -"disabled" +{ + "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 -#### Enumerated Values - -| Value | -| -------------- | -| `disabled` | -| `initializing` | -| `healthy` | -| `unhealthy` | - -## codersdk.WorkspaceAppSharingLevel - -```json -"owner" -``` - -### 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 -| Value | -| --------------- | -| `owner` | -| `authenticated` | -| `public` | +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | -## codersdk.WorkspaceBuild +## healthsdk.DERPRegionReport ```json { - "build_number": 0, - "created_at": "2019-08-24T14:15:22Z", - "daily_cost": 0, - "deadline": "2019-08-24T14:15:22Z", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", - "initiator_name": "string", - "job": { - "canceled_at": "2019-08-24T14:15:22Z", - "completed_at": "2019-08-24T14:15:22Z", - "created_at": "2019-08-24T14:15:22Z", - "error": "string", - "error_code": "REQUIRED_TEMPLATE_VARIABLES", - "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "queue_position": 0, - "queue_size": 0, - "started_at": "2019-08-24T14:15:22Z", - "status": "pending", - "tags": { - "property1": "string", - "property2": "string" - }, - "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" - }, - "max_deadline": "2019-08-24T14:15:22Z", - "reason": "initiator", - "resources": [ + "error": "string", + "healthy": true, + "node_reports": [ { - "agents": [ - { - "api_version": "string", - "apps": [ - { - "command": "string", - "display_name": "string", - "external": true, - "health": "disabled", - "healthcheck": { - "interval": 0, - "threshold": 0, - "url": "string" - }, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "sharing_level": "owner", - "slug": "string", - "subdomain": true, - "subdomain_name": "string", - "url": "string" - } - ], - "architecture": "string", - "connection_timeout_seconds": 0, - "created_at": "2019-08-24T14:15:22Z", - "directory": "string", - "disconnected_at": "2019-08-24T14:15:22Z", - "display_apps": ["vscode"], - "environment_variables": { - "property1": "string", - "property2": "string" - }, - "expanded_directory": "string", - "first_connected_at": "2019-08-24T14:15:22Z", - "health": { - "healthy": false, - "reason": "agent has lost connection" - }, - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "instance_id": "string", - "last_connected_at": "2019-08-24T14:15:22Z", - "latency": { - "property1": { - "latency_ms": 0, - "preferred": true - }, - "property2": { - "latency_ms": 0, - "preferred": true - } - }, - "lifecycle_state": "created", - "log_sources": [ - { - "created_at": "2019-08-24T14:15:22Z", - "display_name": "string", - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" - } - ], - "logs_length": 0, - "logs_overflowed": true, - "name": "string", - "operating_system": "string", - "ready_at": "2019-08-24T14:15:22Z", - "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "scripts": [ - { - "cron": "string", - "log_path": "string", - "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", - "run_on_start": true, - "run_on_stop": true, - "script": "string", - "start_blocks_login": true, - "timeout": 0 - } - ], - "started_at": "2019-08-24T14:15:22Z", - "startup_script_behavior": "blocking", - "status": "connecting", - "subsystems": ["envbox"], - "troubleshooting_url": "string", - "updated_at": "2019-08-24T14:15:22Z", - "version": "string" - } - ], - "created_at": "2019-08-24T14:15:22Z", - "daily_cost": 0, - "hide": true, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", - "metadata": [ + "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": [ { - "key": "string", - "sensitive": true, - "value": "string" + "code": "EUNKNOWN", + "message": "string" } - ], - "name": "string", - "type": "string", - "workspace_transition": "start" + ] } ], - "status": "pending", - "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", - "template_version_name": "string", - "transition": "start", - "updated_at": "2019-08-24T14:15:22Z", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9", - "workspace_name": "string", - "workspace_owner_avatar_url": "string", - "workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7", - "workspace_owner_name": "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 | -| ---------------------------- | ----------------------------------------------------------------- | -------- | ------------ | ----------- | -| `build_number` | integer | false | | | -| `created_at` | string | false | | | -| `daily_cost` | integer | false | | | -| `deadline` | string | false | | | -| `id` | string | false | | | -| `initiator_id` | string | false | | | -| `initiator_name` | string | false | | | -| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | | -| `max_deadline` | string | false | | | -| `reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | | -| `resources` | array of [codersdk.WorkspaceResource](#codersdkworkspaceresource) | false | | | -| `status` | [codersdk.WorkspaceStatus](#codersdkworkspacestatus) | false | | | -| `template_version_id` | string | false | | | -| `template_version_name` | string | false | | | -| `transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | | -| `updated_at` | string | false | | | -| `workspace_id` | string | false | | | -| `workspace_name` | string | false | | | -| `workspace_owner_avatar_url` | string | false | | | -| `workspace_owner_id` | string | false | | | -| `workspace_owner_name` | string | false | | | +| 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 | -| ------------ | ----------- | -| `reason` | `initiator` | -| `reason` | `autostart` | -| `reason` | `autostop` | -| `status` | `pending` | -| `status` | `starting` | -| `status` | `running` | -| `status` | `stopping` | -| `status` | `stopped` | -| `status` | `failed` | -| `status` | `canceling` | -| `status` | `canceled` | -| `status` | `deleting` | -| `status` | `deleted` | -| `transition` | `start` | -| `transition` | `stop` | -| `transition` | `delete` | +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | -## codersdk.WorkspaceBuildParameter +## healthsdk.DatabaseReport ```json { - "name": "string", - "value": "string" + "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 | -| ------- | ------ | -------- | ------------ | ----------- | -| `name` | string | false | | | -| `value` | string | false | | | - -## codersdk.WorkspaceConnectionLatencyMS - -```json -{ - "p50": 0, - "p95": 0 -} -``` +| 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 | | | -### Properties +#### Enumerated Values -| Name | Type | Required | Restrictions | Description | -| ----- | ------ | -------- | ------------ | ----------- | -| `p50` | number | false | | | -| `p95` | number | false | | | +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | -## codersdk.WorkspaceDeploymentStats +## healthsdk.HealthSection ```json -{ - "building": 0, - "connection_latency_ms": { - "p50": 0, - "p95": 0 - }, - "failed": 0, - "pending": 0, - "running": 0, - "rx_bytes": 0, - "stopped": 0, - "tx_bytes": 0 -} +"DERP" ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ----------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `building` | integer | false | | | -| `connection_latency_ms` | [codersdk.WorkspaceConnectionLatencyMS](#codersdkworkspaceconnectionlatencyms) | false | | | -| `failed` | integer | false | | | -| `pending` | integer | false | | | -| `running` | integer | false | | | -| `rx_bytes` | integer | false | | | -| `stopped` | integer | false | | | -| `tx_bytes` | integer | false | | | - -## codersdk.WorkspaceHealth - -```json -{ - "failing_agents": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "healthy": false -} -``` - -### Properties +#### Enumerated Values -| Name | Type | Required | Restrictions | Description | -| ---------------- | --------------- | -------- | ------------ | -------------------------------------------------------------------- | -| `failing_agents` | array of string | false | | Failing agents lists the IDs of the agents that are failing, if any. | -| `healthy` | boolean | false | | Healthy is true if the workspace is healthy. | +| Value | +| -------------------- | +| `DERP` | +| `AccessURL` | +| `Websocket` | +| `Database` | +| `WorkspaceProxy` | +| `ProvisionerDaemons` | -## codersdk.WorkspaceProxy +## healthsdk.HealthSettings ```json { - "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" + "dismissed_healthchecks": ["DERP"] } ``` -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------- | -------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `created_at` | string | false | | | -| `deleted` | boolean | false | | | -| `derp_enabled` | boolean | false | | | -| `derp_only` | boolean | false | | | -| `display_name` | string | false | | | -| `healthy` | boolean | false | | | -| `icon_url` | string | false | | | -| `id` | string | false | | | -| `name` | string | false | | | -| `path_app_url` | string | false | | Path app URL is the URL to the base path for path apps. Optional unless wildcard_hostname is set. E.g. https://us.example.com | -| `status` | [codersdk.WorkspaceProxyStatus](#codersdkworkspaceproxystatus) | false | | Status is the latest status check of the proxy. This will be empty for deleted proxies. This value can be used to determine if a workspace proxy is healthy and ready to use. | -| `updated_at` | string | false | | | -| `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. | +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------------ | ----------------------------------------------------------- | -------- | ------------ | ----------- | +| `dismissed_healthchecks` | array of [healthsdk.HealthSection](#healthsdkhealthsection) | false | | | -## codersdk.WorkspaceProxyReport +## healthsdk.HealthcheckReport ```json { - "dismissed": true, - "error": "string", - "healthy": true, - "severity": "ok", - "warnings": [ - { - "code": "EUNKNOWN", - "message": "string" - } - ], - "workspace_proxies": { - "regions": [ + "access_url": { + "access_url": "string", + "dismissed": true, + "error": "string", + "healthy": true, + "healthz_response": "string", + "reachable": true, + "severity": "ok", + "status_code": 0, + "warnings": [ { - "created_at": "2019-08-24T14:15:22Z", - "deleted": true, - "derp_enabled": true, - "derp_only": true, - "display_name": "string", + "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, - "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"] + "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" }, - "status": "ok" + "version": "string" }, - "updated_at": "2019-08-24T14:15:22Z", - "version": "string", - "wildcard_hostname": "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 | | | -| `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 -{ - "checked_at": "2019-08-24T14:15:22Z", - "report": { - "errors": ["string"], + }, + "severity": "ok", + "time": "2019-08-24T14:15:22Z", + "websocket": { + "body": "string", + "code": 0, + "dismissed": true, + "error": "string", + "healthy": true, + "severity": "ok", "warnings": ["string"] }, - "status": "ok" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | -------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------- | -| `checked_at` | string | false | | | -| `report` | [codersdk.ProxyHealthReport](#codersdkproxyhealthreport) | false | | Report provides more information about the health of the workspace proxy. | -| `status` | [codersdk.ProxyHealthStatus](#codersdkproxyhealthstatus) | false | | | - -## codersdk.WorkspaceQuota - -```json -{ - "budget": 0, - "credits_consumed": 0 -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------- | -------- | ------------ | ----------- | -| `budget` | integer | false | | | -| `credits_consumed` | integer | false | | | - -## codersdk.WorkspaceResource - -```json -{ - "agents": [ - { - "api_version": "string", - "apps": [ - { - "command": "string", - "display_name": "string", - "external": true, - "health": "disabled", - "healthcheck": { - "interval": 0, - "threshold": 0, - "url": "string" - }, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "sharing_level": "owner", - "slug": "string", - "subdomain": true, - "subdomain_name": "string", - "url": "string" - } - ], - "architecture": "string", - "connection_timeout_seconds": 0, - "created_at": "2019-08-24T14:15:22Z", - "directory": "string", - "disconnected_at": "2019-08-24T14:15:22Z", - "display_apps": ["vscode"], - "environment_variables": { - "property1": "string", - "property2": "string" - }, - "expanded_directory": "string", - "first_connected_at": "2019-08-24T14:15:22Z", - "health": { - "healthy": false, - "reason": "agent has lost connection" - }, - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "instance_id": "string", - "last_connected_at": "2019-08-24T14:15:22Z", - "latency": { - "property1": { - "latency_ms": 0, - "preferred": true - }, - "property2": { - "latency_ms": 0, - "preferred": true - } - }, - "lifecycle_state": "created", - "log_sources": [ - { - "created_at": "2019-08-24T14:15:22Z", - "display_name": "string", - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" - } - ], - "logs_length": 0, - "logs_overflowed": true, - "name": "string", - "operating_system": "string", - "ready_at": "2019-08-24T14:15:22Z", - "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "scripts": [ - { - "cron": "string", - "log_path": "string", - "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", - "run_on_start": true, - "run_on_stop": true, - "script": "string", - "start_blocks_login": true, - "timeout": 0 - } - ], - "started_at": "2019-08-24T14:15:22Z", - "startup_script_behavior": "blocking", - "status": "connecting", - "subsystems": ["envbox"], - "troubleshooting_url": "string", - "updated_at": "2019-08-24T14:15:22Z", - "version": "string" - } - ], - "created_at": "2019-08-24T14:15:22Z", - "daily_cost": 0, - "hide": true, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", - "metadata": [ - { - "key": "string", - "sensitive": true, - "value": "string" - } - ], - "name": "string", - "type": "string", - "workspace_transition": "start" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ---------------------- | --------------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `agents` | array of [codersdk.WorkspaceAgent](#codersdkworkspaceagent) | false | | | -| `created_at` | string | false | | | -| `daily_cost` | integer | false | | | -| `hide` | boolean | false | | | -| `icon` | string | false | | | -| `id` | string | false | | | -| `job_id` | string | false | | | -| `metadata` | array of [codersdk.WorkspaceResourceMetadata](#codersdkworkspaceresourcemetadata) | false | | | -| `name` | string | false | | | -| `type` | string | false | | | -| `workspace_transition` | [codersdk.WorkspaceTransition](#codersdkworkspacetransition) | false | | | - -#### Enumerated Values - -| Property | Value | -| ---------------------- | -------- | -| `workspace_transition` | `start` | -| `workspace_transition` | `stop` | -| `workspace_transition` | `delete` | - -## codersdk.WorkspaceResourceMetadata - -```json -{ - "key": "string", - "sensitive": true, - "value": "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 | -| ----------- | ------- | -------- | ------------ | ----------- | -| `key` | string | false | | | -| `sensitive` | boolean | false | | | -| `value` | string | false | | | - -## codersdk.WorkspaceStatus - -```json -"pending" -``` - -### Properties - -#### Enumerated Values - -| Value | -| ----------- | -| `pending` | -| `starting` | -| `running` | -| `stopping` | -| `stopped` | -| `failed` | -| `canceling` | -| `canceled` | -| `deleting` | -| `deleted` | - -## codersdk.WorkspaceTransition - -```json -"start" -``` - -### 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 -| Value | -| -------- | -| `start` | -| `stop` | -| `delete` | +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | -## codersdk.WorkspacesResponse +## healthsdk.ProvisionerDaemonsReport ```json { - "count": 0, - "workspaces": [ + "dismissed": true, + "error": "string", + "items": [ { - "allow_renames": true, - "automatic_updates": "always", - "autostart_schedule": "string", - "created_at": "2019-08-24T14:15:22Z", - "deleting_at": "2019-08-24T14:15:22Z", - "dormant_at": "2019-08-24T14:15:22Z", - "favorite": true, - "health": { - "failing_agents": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"], - "healthy": false - }, - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "last_used_at": "2019-08-24T14:15:22Z", - "latest_build": { - "build_number": 0, + "provisioner_daemon": { + "api_version": "string", "created_at": "2019-08-24T14:15:22Z", - "daily_cost": 0, - "deadline": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3", - "initiator_name": "string", - "job": { - "canceled_at": "2019-08-24T14:15:22Z", - "completed_at": "2019-08-24T14:15:22Z", - "created_at": "2019-08-24T14:15:22Z", - "error": "string", - "error_code": "REQUIRED_TEMPLATE_VARIABLES", - "file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "queue_position": 0, - "queue_size": 0, - "started_at": "2019-08-24T14:15:22Z", - "status": "pending", - "tags": { - "property1": "string", - "property2": "string" - }, - "worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b" + "last_seen_at": "2019-08-24T14:15:22Z", + "name": "string", + "provisioners": ["string"], + "tags": { + "property1": "string", + "property2": "string" }, - "max_deadline": "2019-08-24T14:15:22Z", - "reason": "initiator", - "resources": [ - { - "agents": [ - { - "api_version": "string", - "apps": [ - { - "command": "string", - "display_name": "string", - "external": true, - "health": "disabled", - "healthcheck": {}, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "sharing_level": "owner", - "slug": "string", - "subdomain": true, - "subdomain_name": "string", - "url": "string" - } - ], - "architecture": "string", - "connection_timeout_seconds": 0, - "created_at": "2019-08-24T14:15:22Z", - "directory": "string", - "disconnected_at": "2019-08-24T14:15:22Z", - "display_apps": ["vscode"], - "environment_variables": { - "property1": "string", - "property2": "string" - }, - "expanded_directory": "string", - "first_connected_at": "2019-08-24T14:15:22Z", - "health": { - "healthy": false, - "reason": "agent has lost connection" - }, - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "instance_id": "string", - "last_connected_at": "2019-08-24T14:15:22Z", - "latency": { - "property1": { - "latency_ms": 0, - "preferred": true - }, - "property2": { - "latency_ms": 0, - "preferred": true - } - }, - "lifecycle_state": "created", - "log_sources": [ - { - "created_at": "2019-08-24T14:15:22Z", - "display_name": "string", - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" - } - ], - "logs_length": 0, - "logs_overflowed": true, - "name": "string", - "operating_system": "string", - "ready_at": "2019-08-24T14:15:22Z", - "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "scripts": [ - { - "cron": "string", - "log_path": "string", - "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", - "run_on_start": true, - "run_on_stop": true, - "script": "string", - "start_blocks_login": true, - "timeout": 0 - } - ], - "started_at": "2019-08-24T14:15:22Z", - "startup_script_behavior": "blocking", - "status": "connecting", - "subsystems": ["envbox"], - "troubleshooting_url": "string", - "updated_at": "2019-08-24T14:15:22Z", - "version": "string" - } - ], - "created_at": "2019-08-24T14:15:22Z", - "daily_cost": 0, - "hide": true, - "icon": "string", - "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", - "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", - "metadata": [ - { - "key": "string", - "sensitive": true, - "value": "string" - } - ], - "name": "string", - "type": "string", - "workspace_transition": "start" - } - ], - "status": "pending", - "template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1", - "template_version_name": "string", - "transition": "start", - "updated_at": "2019-08-24T14:15:22Z", - "workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9", - "workspace_name": "string", - "workspace_owner_avatar_url": "string", - "workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7", - "workspace_owner_name": "string" - }, - "name": "string", - "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", - "outdated": true, - "owner_avatar_url": "string", - "owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05", - "owner_name": "string", - "template_active_version_id": "b0da9c29-67d8-4c87-888c-bafe356f7f3c", - "template_allow_user_cancel_workspace_jobs": true, - "template_display_name": "string", - "template_icon": "string", - "template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc", - "template_name": "string", - "template_require_active_version": true, - "ttl_ms": 0, - "updated_at": "2019-08-24T14:15:22Z" + "version": "string" + }, + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" + } + ] + } + ], + "severity": "ok", + "warnings": [ + { + "code": "EUNKNOWN", + "message": "string" } ] } @@ -8355,109 +8221,167 @@ If the schedule is empty, the user will be updated to use the default schedule.| ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------ | ------------------------------------------------- | -------- | ------------ | ----------- | -| `count` | integer | false | | | -| `workspaces` | array of [codersdk.Workspace](#codersdkworkspace) | false | | | +| 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 | | | -## derp.BytesSentRecv +## healthsdk.ProvisionerDaemonsReportItem ```json { - "key": {}, - "recv": 0, - "sent": 0 + "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 | -| ------ | -------------------------------- | -------- | ------------ | -------------------------------------------------------------------- | -| `key` | [key.NodePublic](#keynodepublic) | false | | Key is the public key of the client which sent/received these bytes. | -| `recv` | integer | false | | | -| `sent` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------------- | -------------------------------------------------------- | -------- | ------------ | ----------- | +| `provisioner_daemon` | [codersdk.ProvisionerDaemon](#codersdkprovisionerdaemon) | false | | | +| `warnings` | array of [health.Message](#healthmessage) | false | | | -## derp.ServerInfoMessage +## healthsdk.STUNReport ```json { - "tokenBucketBytesBurst": 0, - "tokenBucketBytesPerSecond": 0 + "canSTUN": true, + "enabled": true, + "error": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------------------------------------------------------------------------------ | ------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------ | -| `tokenBucketBytesBurst` | integer | false | | Tokenbucketbytesburst is how many bytes the server will allow to burst, temporarily violating TokenBucketBytesPerSecond. | -| Zero means unspecified. There might be a limit, but the client need not try to respect it. | -| `tokenBucketBytesPerSecond` | integer | false | | Tokenbucketbytespersecond is how many bytes per second the server says it will accept, including all framing bytes. | -| Zero means unspecified. There might be a limit, but the client need not try to respect it. | +| Name | Type | Required | Restrictions | Description | +| --------- | ------- | -------- | ------------ | ----------- | +| `canSTUN` | boolean | false | | | +| `enabled` | boolean | false | | | +| `error` | string | false | | | -## health.Code +## healthsdk.UpdateHealthSettings ```json -"EUNKNOWN" +{ + "dismissed_healthchecks": ["DERP"] +} ``` ### Properties -#### Enumerated Values - -| Value | -| ---------- | -| `EUNKNOWN` | -| `EWP01` | -| `EWP02` | -| `EWP04` | -| `EDB01` | -| `EDB02` | -| `EWS01` | -| `EWS02` | -| `EWS03` | -| `EACS01` | -| `EACS02` | -| `EACS03` | -| `EACS04` | -| `EDERP01` | -| `EDERP02` | -| `EPD01` | -| `EPD02` | -| `EPD03` | +| Name | Type | Required | Restrictions | Description | +| ------------------------ | ----------------------------------------------------------- | -------- | ------------ | ----------- | +| `dismissed_healthchecks` | array of [healthsdk.HealthSection](#healthsdkhealthsection) | false | | | -## health.Message +## healthsdk.WebsocketReport ```json { - "code": "EUNKNOWN", - "message": "string" + "body": "string", + "code": 0, + "dismissed": true, + "error": "string", + "healthy": true, + "severity": "ok", + "warnings": ["string"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | -------------------------- | -------- | ------------ | ----------- | -| `code` | [health.Code](#healthcode) | false | | | -| `message` | string | false | | | +| 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 | | | -## health.Severity +#### Enumerated Values + +| Property | Value | +| ---------- | --------- | +| `severity` | `ok` | +| `severity` | `warning` | +| `severity` | `error` | + +## healthsdk.WorkspaceProxyReport ```json -"ok" +{ + "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 -#### Enumerated Values - -| Value | -| --------- | -| `ok` | -| `warning` | -| `error` | +| 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 @@ -9105,6 +9029,82 @@ _None_ | `user_id` | string | false | | | | `workspace_id` | string | false | | | +## workspacesdk.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 | | | + ## wsproxysdk.DeregisterWorkspaceProxyRequest ```json diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index deb4e0636318c..5c8b952b1c018 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; @@ -2082,23 +1940,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[] = [ From d24200e7344b7cad324893eae1096a8c891b4b39 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Sun, 24 Mar 2024 22:41:27 +0000 Subject: [PATCH 04/11] fixup! `make gen` --- scripts/apitypings/main.go | 2 +- site/src/api/typesGenerated.ts | 164 +++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 1 deletion(-) diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index 3093e268d3f24..b1cae7f5a44a8 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 diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 5c8b952b1c018..2e32a416710c1 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2276,6 +2276,170 @@ export const WorkspaceTransitions: WorkspaceTransition[] = [ // From codersdk/workspaceproxy.go export type RegionTypes = Region | WorkspaceProxy; +// The code below is generated from codersdk/healthsdk. + +// From healthsdk/health.go +export interface HealthsdkAccessURLReport { + 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/health.go +export interface HealthsdkDERPHealthReport { + 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/health.go +export interface HealthsdkDERPNodeReport { + 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: HealthsdkSTUNReport; +} + +// From healthsdk/health.go +export interface HealthsdkDERPRegionReport { + 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: HealthsdkDERPNodeReport[]; + readonly error?: string; +} + +// From healthsdk/health.go +export interface HealthsdkDatabaseReport { + 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/health.go +export interface HealthsdkHealthClient {} + +// From healthsdk/health.go +export interface HealthsdkHealthSettings { + readonly dismissed_healthchecks: HealthsdkHealthSection[]; +} + +// From healthsdk/health.go +export interface HealthsdkHealthcheckReport { + readonly time: string; + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly failing_sections: HealthsdkHealthSection[]; + readonly derp: HealthsdkDERPHealthReport; + readonly access_url: HealthsdkAccessURLReport; + readonly websocket: HealthsdkWebsocketReport; + readonly database: HealthsdkDatabaseReport; + readonly workspace_proxy: HealthsdkWorkspaceProxyReport; + readonly provisioner_daemons: HealthsdkProvisionerDaemonsReport; + readonly coder_version: string; +} + +// From healthsdk/health.go +export interface HealthsdkProvisionerDaemonsReport { + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + readonly dismissed: boolean; + readonly error?: string; + readonly items: HealthsdkProvisionerDaemonsReportItem[]; +} + +// From healthsdk/health.go +export interface HealthsdkProvisionerDaemonsReportItem { + readonly provisioner_daemon: ProvisionerDaemon; + readonly warnings: HealthMessage[]; +} + +// From healthsdk/health.go +export interface HealthsdkSTUNReport { + readonly Enabled: boolean; + readonly CanSTUN: boolean; + readonly Error?: string; +} + +// From healthsdk/health.go +export interface HealthsdkUpdateHealthSettings { + readonly dismissed_healthchecks: HealthsdkHealthSection[]; +} + +// From healthsdk/health.go +export interface HealthsdkWebsocketReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: string[]; + readonly dismissed: boolean; + readonly body: string; + readonly code: number; + readonly error?: string; +} + +// From healthsdk/health.go +export interface HealthsdkWorkspaceProxyReport { + readonly healthy: boolean; + readonly severity: HealthSeverity; + readonly warnings: HealthMessage[]; + readonly dismissed: boolean; + readonly error?: string; + readonly workspace_proxies: RegionsResponse; +} + +// From healthsdk/health.go +export type HealthsdkHealthSection = + | "AccessURL" + | "DERP" + | "Database" + | "ProvisionerDaemons" + | "Websocket" + | "WorkspaceProxy"; +export const HealthsdkHealthSections: HealthsdkHealthSection[] = [ + "AccessURL", + "DERP", + "Database", + "ProvisionerDaemons", + "Websocket", + "WorkspaceProxy", +]; + // The code below is generated from coderd/healthcheck/health. // From health/model.go From 8540911ecf5c40b25e2bf5222eb8832fce73d8f7 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 25 Mar 2024 19:48:47 +0000 Subject: [PATCH 05/11] fixup! `make gen` --- codersdk/healthsdk/health.go | 1 + scripts/apitypings/main.go | 2 +- site/src/api/typesGenerated.ts | 59 ++++++++++++++++------------------ 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/codersdk/healthsdk/health.go b/codersdk/healthsdk/health.go index a0ef1e39e182e..eb728b6531d46 100644 --- a/codersdk/healthsdk/health.go +++ b/codersdk/healthsdk/health.go @@ -15,6 +15,7 @@ import ( "github.com/coder/coder/v2/codersdk" ) +// @typescript-ignore HealthClient type HealthClient struct { client *codersdk.Client } diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index b1cae7f5a44a8..6107396eaa8e3 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -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() diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 2e32a416710c1..703a7bac95827 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2279,7 +2279,7 @@ export type RegionTypes = Region | WorkspaceProxy; // The code below is generated from codersdk/healthsdk. // From healthsdk/health.go -export interface HealthsdkAccessURLReport { +export interface AccessURLReport { readonly healthy: boolean; readonly severity: HealthSeverity; readonly warnings: HealthMessage[]; @@ -2292,12 +2292,12 @@ export interface HealthsdkAccessURLReport { } // From healthsdk/health.go -export interface HealthsdkDERPHealthReport { +export interface DERPHealthReport { readonly healthy: boolean; readonly severity: HealthSeverity; readonly warnings: HealthMessage[]; readonly dismissed: boolean; - readonly regions: Record; + 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; @@ -2307,7 +2307,7 @@ export interface HealthsdkDERPHealthReport { } // From healthsdk/health.go -export interface HealthsdkDERPNodeReport { +export interface DERPNodeReport { readonly healthy: boolean; readonly severity: HealthSeverity; readonly warnings: HealthMessage[]; @@ -2324,23 +2324,23 @@ export interface HealthsdkDERPNodeReport { readonly client_logs: string[][]; readonly client_errs: string[][]; readonly error?: string; - readonly stun: HealthsdkSTUNReport; + readonly stun: STUNReport; } // From healthsdk/health.go -export interface HealthsdkDERPRegionReport { +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: HealthsdkDERPNodeReport[]; + readonly node_reports: DERPNodeReport[]; readonly error?: string; } // From healthsdk/health.go -export interface HealthsdkDatabaseReport { +export interface DatabaseReport { readonly healthy: boolean; readonly severity: HealthSeverity; readonly warnings: HealthMessage[]; @@ -2353,57 +2353,54 @@ export interface HealthsdkDatabaseReport { } // From healthsdk/health.go -export interface HealthsdkHealthClient {} - -// From healthsdk/health.go -export interface HealthsdkHealthSettings { - readonly dismissed_healthchecks: HealthsdkHealthSection[]; +export interface HealthSettings { + readonly dismissed_healthchecks: HealthSection[]; } // From healthsdk/health.go -export interface HealthsdkHealthcheckReport { +export interface HealthcheckReport { readonly time: string; readonly healthy: boolean; readonly severity: HealthSeverity; - readonly failing_sections: HealthsdkHealthSection[]; - readonly derp: HealthsdkDERPHealthReport; - readonly access_url: HealthsdkAccessURLReport; - readonly websocket: HealthsdkWebsocketReport; - readonly database: HealthsdkDatabaseReport; - readonly workspace_proxy: HealthsdkWorkspaceProxyReport; - readonly provisioner_daemons: HealthsdkProvisionerDaemonsReport; + 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/health.go -export interface HealthsdkProvisionerDaemonsReport { +export interface ProvisionerDaemonsReport { readonly severity: HealthSeverity; readonly warnings: HealthMessage[]; readonly dismissed: boolean; readonly error?: string; - readonly items: HealthsdkProvisionerDaemonsReportItem[]; + readonly items: ProvisionerDaemonsReportItem[]; } // From healthsdk/health.go -export interface HealthsdkProvisionerDaemonsReportItem { +export interface ProvisionerDaemonsReportItem { readonly provisioner_daemon: ProvisionerDaemon; readonly warnings: HealthMessage[]; } // From healthsdk/health.go -export interface HealthsdkSTUNReport { +export interface STUNReport { readonly Enabled: boolean; readonly CanSTUN: boolean; readonly Error?: string; } // From healthsdk/health.go -export interface HealthsdkUpdateHealthSettings { - readonly dismissed_healthchecks: HealthsdkHealthSection[]; +export interface UpdateHealthSettings { + readonly dismissed_healthchecks: HealthSection[]; } // From healthsdk/health.go -export interface HealthsdkWebsocketReport { +export interface WebsocketReport { readonly healthy: boolean; readonly severity: HealthSeverity; readonly warnings: string[]; @@ -2414,7 +2411,7 @@ export interface HealthsdkWebsocketReport { } // From healthsdk/health.go -export interface HealthsdkWorkspaceProxyReport { +export interface WorkspaceProxyReport { readonly healthy: boolean; readonly severity: HealthSeverity; readonly warnings: HealthMessage[]; @@ -2424,14 +2421,14 @@ export interface HealthsdkWorkspaceProxyReport { } // From healthsdk/health.go -export type HealthsdkHealthSection = +export type HealthSection = | "AccessURL" | "DERP" | "Database" | "ProvisionerDaemons" | "Websocket" | "WorkspaceProxy"; -export const HealthsdkHealthSections: HealthsdkHealthSection[] = [ +export const HealthSections: HealthSection[] = [ "AccessURL", "DERP", "Database", From 9f61acb6d149897c6e9bd739f9f8a6c8989d32f7 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 25 Mar 2024 20:09:44 +0000 Subject: [PATCH 06/11] remove all workspace prefixes from `workspacesdk` --- agent/agent.go | 14 +-- agent/agent_test.go | 8 +- agent/ports_supported.go | 2 +- cli/agent_test.go | 10 +-- cli/configssh_test.go | 4 +- cli/exp_scaletest.go | 2 +- cli/netcheck.go | 2 +- cli/ping.go | 4 +- cli/portforward.go | 6 +- cli/speedtest.go | 4 +- cli/ssh.go | 4 +- cli/support_test.go | 2 +- cli/vscodessh.go | 6 +- coderd/activitybump_test.go | 8 +- coderd/coderd_test.go | 8 +- coderd/insights_test.go | 12 +-- coderd/tailnet.go | 6 +- coderd/tailnet_test.go | 10 +-- coderd/templates_test.go | 4 +- coderd/workspaceagents.go | 8 +- coderd/workspaceagents_test.go | 32 +++---- coderd/workspaceapps/apptest/apptest.go | 4 +- coderd/workspaceapps/proxy.go | 6 +- codersdk/workspacesdk/workspaceagentconn.go | 96 ++++++++++----------- codersdk/workspacesdk/workspaceagents.go | 44 +++++----- enterprise/coderd/coderd_test.go | 2 +- enterprise/coderd/replicas_test.go | 8 +- enterprise/coderd/workspaceagents_test.go | 4 +- enterprise/tailnet/pgcoord_test.go | 2 +- enterprise/wsproxy/wsproxy_test.go | 8 +- enterprise/wsproxy/wsproxysdk/wsproxysdk.go | 4 +- scaletest/agentconn/run.go | 18 ++-- scaletest/createworkspaces/run_test.go | 4 +- scaletest/reconnectingpty/config.go | 2 +- scaletest/reconnectingpty/config_test.go | 2 +- scaletest/reconnectingpty/run.go | 2 +- scaletest/reconnectingpty/run_test.go | 16 ++-- scaletest/workspacetraffic/conn.go | 4 +- support/support.go | 12 +-- tailnet/coordinator_test.go | 2 +- 40 files changed, 198 insertions(+), 198 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 2f7102c190ae2..0cb2aa2acad9e 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -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(workspacesdk.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(workspacesdk.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(workspacesdk.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 workspacesdk.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(workspacesdk.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(workspacesdk.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 workspacesdk.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 25db07864f6f2..2813d45125a6d 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -1783,7 +1783,7 @@ func TestAgent_UpdatedDERP(t *testing.T) { }) // Setup a client connection. - newClientConn := func(derpMap *tailcfg.DERPMap, name string) *workspacesdk.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,7 +1812,7 @@ func TestAgent_UpdatedDERP(t *testing.T) { // Force DERP. conn.SetBlockEndpoints(true) - sdkConn := workspacesdk.NewWorkspaceAgentConn(conn, workspacesdk.WorkspaceAgentConnOptions{ + sdkConn := workspacesdk.NewAgentConn(conn, workspacesdk.AgentConnOptions{ AgentID: agentID, CloseFunc: func() error { return workspacesdk.ErrSkipClose }, }) @@ -2223,7 +2223,7 @@ func setupSSHSession( } func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Duration, opts ...func(*agenttest.Client, *agent.Options)) ( - *workspacesdk.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 := workspacesdk.NewWorkspaceAgentConn(conn, workspacesdk.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 079eaa5c92d30..efa554de983d3 100644 --- a/agent/ports_supported.go +++ b/agent/ports_supported.go @@ -33,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 < workspacesdk.WorkspaceAgentMinimumListeningPort { + if tab.LocalAddr == nil || tab.LocalAddr.Port < workspacesdk.AgentMinimumListeningPort { continue } diff --git a/cli/agent_test.go b/cli/agent_test.go index 25551ff203303..bc283d272f8e9 100644 --- a/cli/agent_test.go +++ b/cli/agent_test.go @@ -92,8 +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 := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil) + dialer, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer dialer.Close() require.True(t, dialer.AwaitReachable(ctx)) @@ -132,8 +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 := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil) + dialer, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer dialer.Close() require.True(t, dialer.AwaitReachable(ctx)) @@ -176,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 := workspacesdk.NewWorkspaceClient(client).DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil) + dialer, err := workspacesdk.NewClient(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 05e98cf7d4512..1a0399bde60d0 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -84,8 +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 := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(context.Background(), resources[0].Agents[0].ID, nil) + agentConn, err := workspacesdk.NewClient(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 6ced0e3a95a3f..3f5468635d934 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -668,7 +668,7 @@ func (r *RootCmd) scaletestCreateWorkspaces() *serpent.Command { if runCommand != "" { config.ReconnectingPTY = &reconnectingpty.Config{ // AgentID is set by the test automatically. - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ ID: uuid.Nil, Height: 24, Width: 80, diff --git a/cli/netcheck.go b/cli/netcheck.go index 89840948f8254..9823a0deafd89 100644 --- a/cli/netcheck.go +++ b/cli/netcheck.go @@ -27,7 +27,7 @@ func (r *RootCmd) netcheck() *serpent.Command { ctx, cancel := context.WithTimeout(inv.Context(), 30*time.Second) defer cancel() - connInfo, err := workspacesdk.NewWorkspaceClient(client).WorkspaceAgentConnectionInfoGeneric(ctx) + connInfo, err := workspacesdk.NewClient(client).AgentConnectionInfoGeneric(ctx) if err != nil { return err } diff --git a/cli/ping.go b/cli/ping.go index faa2b058522ed..fbf6c46fd6b24 100644 --- a/cli/ping.go +++ b/cli/ping.go @@ -56,8 +56,8 @@ func (r *RootCmd) ping() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, }) diff --git a/cli/portforward.go b/cli/portforward.go index 7d6b55e117f64..70f462d08a529 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -103,8 +103,8 @@ func (r *RootCmd) portForward() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, }) @@ -195,7 +195,7 @@ func (r *RootCmd) portForward() *serpent.Command { func listenAndPortForward( ctx context.Context, inv *serpent.Invocation, - conn *workspacesdk.WorkspaceAgentConn, + conn *workspacesdk.AgentConn, wg *sync.WaitGroup, spec portForwardSpec, logger slog.Logger, diff --git a/cli/speedtest.go b/cli/speedtest.go index 29368138f1e9f..7f208b9941fc8 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -60,8 +60,8 @@ func (r *RootCmd) speedtest() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, }) if err != nil { diff --git a/cli/ssh.go b/cli/ssh.go index 78d22c3067db4..32b28a9c64ad1 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -223,8 +223,8 @@ func (r *RootCmd) ssh() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, }) diff --git a/cli/support_test.go b/cli/support_test.go index 0c2f15655c35c..7f2fce53e4836 100644 --- a/cli/support_test.go +++ b/cli/support_test.go @@ -171,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 workspacesdk.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 8a398b572870e..2c22c6a622522 100644 --- a/cli/vscodessh.go +++ b/cli/vscodessh.go @@ -165,8 +165,8 @@ func (r *RootCmd) vscodeSSH() *serpent.Command { if r.disableDirect { logger.Info(ctx, "direct connections disabled") } - agentConn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, workspaceAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + agentConn, err := workspacesdk.NewClient(client). + DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, }) @@ -282,7 +282,7 @@ type sshNetworkStats struct { DownloadBytesSec int64 `json:"download_bytes_sec"` } -func collectNetworkStats(ctx context.Context, agentConn *workspacesdk.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 d318cbd8a55f4..93e6bd7fe3038 100644 --- a/coderd/activitybump_test.go +++ b/coderd/activitybump_test.go @@ -166,8 +166,8 @@ func TestWorkspaceActivityBump(t *testing.T) { client, workspace, assertBumped := setupActivityTest(t) resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil), }) require.NoError(t, err) @@ -204,8 +204,8 @@ func TestWorkspaceActivityBump(t *testing.T) { // Bump by dialing the workspace and sending traffic. resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil), }) require.NoError(t, err) diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index 34f821d89b7cc..51613775bd736 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -190,10 +190,10 @@ func TestDERPForceWebSockets(t *testing.T) { t.Cleanup(func() { client.HTTPClient.CloseIdleConnections() }) - wsclient := workspacesdk.NewWorkspaceClient(client) + wsclient := workspacesdk.NewClient(client) user := coderdtest.CreateFirstUser(t, client) - gen, err := wsclient.WorkspaceAgentConnectionInfoGeneric(context.Background()) + gen, err := wsclient.AgentConnectionInfoGeneric(context.Background()) require.NoError(t, err) t.Log(spew.Sdump(gen)) @@ -215,8 +215,8 @@ func TestDERPForceWebSockets(t *testing.T) { defer cancel() resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - conn, err := wsclient.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, - &workspacesdk.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/insights_test.go b/coderd/insights_test.go index 684ae73be54fd..9be9929c58d94 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -87,8 +87,8 @@ func TestDeploymentInsights(t *testing.T) { require.NoError(t, err) assert.NotZero(t, res.Workspaces[0].LastUsedAt) - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Named("tailnet"), }) require.NoError(t, err) @@ -176,8 +176,8 @@ func TestUserActivityInsights_SanityCheck(t *testing.T) { defer cancel() // Connect to the agent to generate usage/latency stats. - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: logger.Named("client"), }) require.NoError(t, err) @@ -274,8 +274,8 @@ func TestUserLatencyInsights(t *testing.T) { defer cancel() // Connect to the agent to generate usage/latency stats. - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: logger.Named("client"), }) require.NoError(t, err) diff --git a/coderd/tailnet.go b/coderd/tailnet.go index 170770fa96efc..f684b05cd2756 100644 --- a/coderd/tailnet.go +++ b/coderd/tailnet.go @@ -427,9 +427,9 @@ func (s *ServerTailnet) acquireTicket(agentID uuid.UUID) (release func()) { } } -func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*workspacesdk.WorkspaceAgentConn, func(), error) { +func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*workspacesdk.AgentConn, func(), error) { var ( - conn *workspacesdk.WorkspaceAgentConn + conn *workspacesdk.AgentConn ret func() ) @@ -440,7 +440,7 @@ func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*work } ret = s.acquireTicket(agentID) - conn = workspacesdk.NewWorkspaceAgentConn(s.conn, workspacesdk.WorkspaceAgentConnOptions{ + conn = workspacesdk.NewAgentConn(s.conn, workspacesdk.AgentConnOptions{ AgentID: agentID, CloseFunc: func() error { return workspacesdk.ErrSkipClose }, }) diff --git a/coderd/tailnet_test.go b/coderd/tailnet_test.go index 20d2ee56b7700..b7b7ad1df938c 100644 --- a/coderd/tailnet_test.go +++ b/coderd/tailnet_test.go @@ -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", workspacesdk.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", workspacesdk.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", workspacesdk.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(), workspacesdk.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", workspacesdk.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 99dc542da69b1..82eecbf5b33d0 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -1236,8 +1236,8 @@ func TestTemplateMetrics(t *testing.T) { require.NoError(t, err) assert.Zero(t, res.Workspaces[0].LastUsedAt) - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Named("tailnet"), }) require.NoError(t, err) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 3ce97accb5713..020bbc1c9cb83 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -804,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 < workspacesdk.WorkspaceAgentMinimumListeningPort { + if port.Port < workspacesdk.AgentMinimumListeningPort { continue } if _, ok := appPorts[port.Port]; ok { continue } - if _, ok := workspacesdk.WorkspaceAgentIgnoredListeningPorts[port.Port]; ok { + if _, ok := workspacesdk.AgentIgnoredListeningPorts[port.Port]; ok { continue } filteredPorts = append(filteredPorts, port) @@ -831,7 +831,7 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() - httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.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(), @@ -852,7 +852,7 @@ func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request func (api *API) workspaceAgentConnectionGeneric(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() - httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.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 7352e30dc2322..7baf4be58974d 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -338,8 +338,8 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil) + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer func() { _ = conn.Close() @@ -450,12 +450,12 @@ func TestWorkspaceAgentTailnet(t *testing.T) { _ = agenttest.New(t, client.URL, r.AgentToken) resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID) - conn, err := func() (*workspacesdk.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 workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + return workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), }) }() @@ -550,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 workspacesdk.WorkspaceAgentConnectionInfo + var connInfo workspacesdk.AgentConnectionInfo err = json.NewDecoder(res.Body).Decode(&connInfo) require.NoError(t, err) require.True(t, connInfo.DisableDirectConnections) @@ -566,8 +566,8 @@ func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) { } } - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), }) require.NoError(t, err) @@ -607,10 +607,10 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) { } willFilterPort := func(port int) bool { - if port < workspacesdk.WorkspaceAgentMinimumListeningPort || port > 65535 { + if port < workspacesdk.AgentMinimumListeningPort || port > 65535 { return true } - if _, ok := workspacesdk.WorkspaceAgentIgnoredListeningPorts[uint16(port)]; ok { + if _, ok := workspacesdk.AgentIgnoredListeningPorts[uint16(port)]; ok { return true } @@ -650,7 +650,7 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) { port uint16 ) require.Eventually(t, func() bool { - for ignoredPort := range workspacesdk.WorkspaceAgentIgnoredListeningPorts { + for ignoredPort := range workspacesdk.AgentIgnoredListeningPorts { if ignoredPort < 1024 || ignoredPort == 5432 { continue } @@ -1627,12 +1627,12 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) { agentID := resources[0].Agents[0].ID // Connect from a client. - conn1, err := func() (*workspacesdk.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 workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{ + return workspacesdk.NewClient(client). + DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ Logger: logger.Named("client1"), }) }() @@ -1677,8 +1677,8 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) { require.True(t, ok) // Connect from a second client. - conn2, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{ + conn2, err := workspacesdk.NewClient(client). + DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ Logger: logger.Named("client2"), }) require.NoError(t, err) diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go index d8b4a476dbb51..0503f5b087f2a 100644 --- a/coderd/workspaceapps/apptest/apptest.go +++ b/coderd/workspaceapps/apptest/apptest.go @@ -951,7 +951,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) { defer cancel() app := appDetails.Apps.Port - app.AppSlugOrPort = strconv.Itoa(workspacesdk.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() @@ -1721,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 := workspacesdk.NewWorkspaceClient(client).WorkspaceAgentReconnectingPTY(ctx, opts) + conn, err := workspacesdk.NewClient(client).AgentReconnectingPTY(ctx, opts) require.NoError(t, err) defer conn.Close() diff --git a/coderd/workspaceapps/proxy.go b/coderd/workspaceapps/proxy.go index 94a090375e572..5b14cbf340165 100644 --- a/coderd/workspaceapps/proxy.go +++ b/coderd/workspaceapps/proxy.go @@ -69,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) (_ *workspacesdk.WorkspaceAgentConn, release func(), _ error) + AgentConn(ctx context.Context, agentID uuid.UUID) (_ *workspacesdk.AgentConn, release func(), _ error) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) @@ -514,10 +514,10 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT return } - if portInt < workspacesdk.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, workspacesdk.WorkspaceAgentMinimumListeningPort, + portInt, workspacesdk.AgentMinimumListeningPort, ), }) return diff --git a/codersdk/workspacesdk/workspaceagentconn.go b/codersdk/workspacesdk/workspaceagentconn.go index e8a1a359c06df..55de430efd0f5 100644 --- a/codersdk/workspacesdk/workspaceagentconn.go +++ b/codersdk/workspacesdk/workspaceagentconn.go @@ -27,26 +27,26 @@ import ( "github.com/coder/coder/v2/tailnet" ) -// WorkspaceAgentIP is a static IPv6 address with the Tailscale prefix that is used to route +// 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 WorkspaceAgentIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4") +var AgentIP = 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. + AgentSSHPort = tailnet.WorkspaceAgentSSHPort + AgentReconnectingPTYPort = tailnet.WorkspaceAgentReconnectingPTYPort + AgentSpeedtestPort = tailnet.WorkspaceAgentSpeedtestPort + // AgentHTTPAPIServerPort serves a HTTP server with endpoints for e.g. // gathering agent statistics. - WorkspaceAgentHTTPAPIServerPort = 4 + AgentHTTPAPIServerPort = 4 - // WorkspaceAgentMinimumListeningPort is the minimum port that the listening-ports + // 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 @@ -55,15 +55,15 @@ const ( // 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 + AgentMinimumListeningPort = 9 ) -// WorkspaceAgentIgnoredListeningPorts contains a list of ports to ignore when looking for +// 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 WorkspaceAgentIgnoredListeningPorts = map[uint16]struct{}{ +var AgentIgnoredListeningPorts = map[uint16]struct{}{ 0: {}, // Ports 1-8 are reserved for future use by the Coder agent. 1: {}, @@ -125,40 +125,40 @@ func init() { // 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{}{} + AgentIgnoredListeningPorts[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() @@ -167,7 +167,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() @@ -175,7 +175,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() @@ -189,9 +189,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 @@ -210,7 +210,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() @@ -218,11 +218,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, @@ -245,7 +245,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() @@ -253,12 +253,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() @@ -281,7 +281,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() @@ -289,7 +289,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) } @@ -304,7 +304,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() @@ -327,7 +327,7 @@ func (c *WorkspaceAgentConn) DialContext(ctx context.Context, network string, ad } // ListeningPorts lists the ports that are currently in use by the workspace. -func (c *WorkspaceAgentConn) ListeningPorts(ctx context.Context) (codersdk.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) @@ -344,7 +344,7 @@ func (c *WorkspaceAgentConn) ListeningPorts(ctx context.Context) (codersdk.Works } // 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) @@ -364,7 +364,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) @@ -383,7 +383,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) @@ -402,7 +402,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) @@ -421,11 +421,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) @@ -438,7 +438,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 @@ -455,7 +455,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) } @@ -468,7 +468,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) } @@ -479,6 +479,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/workspaceagents.go b/codersdk/workspacesdk/workspaceagents.go index ab26bc216e70d..c42dd3237c488 100644 --- a/codersdk/workspacesdk/workspaceagents.go +++ b/codersdk/workspacesdk/workspaceagents.go @@ -26,65 +26,65 @@ import ( "github.com/coder/retry" ) -type WorkspaceClient struct { +type Client struct { client *codersdk.Client } -func NewWorkspaceClient(c *codersdk.Client) *WorkspaceClient { - return &WorkspaceClient{client: c} +func NewClient(c *codersdk.Client) *Client { + return &Client{client: c} } -// WorkspaceAgentConnectionInfo returns required information for establishing +// AgentConnectionInfo returns required information for establishing // a connection with a workspace. -// @typescript-ignore WorkspaceAgentConnectionInfo -type WorkspaceAgentConnectionInfo struct { +// @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 *WorkspaceClient) WorkspaceAgentConnectionInfoGeneric(ctx context.Context) (WorkspaceAgentConnectionInfo, error) { +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 WorkspaceAgentConnectionInfo{}, err + return AgentConnectionInfo{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return WorkspaceAgentConnectionInfo{}, codersdk.ReadBodyAsError(res) + return AgentConnectionInfo{}, codersdk.ReadBodyAsError(res) } - var connInfo WorkspaceAgentConnectionInfo + var connInfo AgentConnectionInfo return connInfo, json.NewDecoder(res.Body).Decode(&connInfo) } -func (c *WorkspaceClient) WorkspaceAgentConnectionInfo(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentConnectionInfo, error) { +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 WorkspaceAgentConnectionInfo{}, err + return AgentConnectionInfo{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return WorkspaceAgentConnectionInfo{}, codersdk.ReadBodyAsError(res) + return AgentConnectionInfo{}, codersdk.ReadBodyAsError(res) } - var connInfo WorkspaceAgentConnectionInfo + var connInfo AgentConnectionInfo return connInfo, json.NewDecoder(res.Body).Decode(&connInfo) } -// @typescript-ignore DialWorkspaceAgentOptions -type DialWorkspaceAgentOptions struct { +// @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 *WorkspaceClient) DialWorkspaceAgent(dialCtx context.Context, agentID uuid.UUID, options *DialWorkspaceAgentOptions) (agentConn *WorkspaceAgentConn, err error) { +func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *DialAgentOptions) (agentConn *AgentConn, err error) { if options == nil { - options = &DialWorkspaceAgentOptions{} + options = &DialAgentOptions{} } - connInfo, err := c.WorkspaceAgentConnectionInfo(dialCtx, agentID) + connInfo, err := c.AgentConnectionInfo(dialCtx, agentID) if err != nil { return nil, xerrors.Errorf("get connection info: %w", err) } @@ -161,7 +161,7 @@ func (c *WorkspaceClient) DialWorkspaceAgent(dialCtx context.Context, agentID uu options.Logger.Debug(ctx, "connected to tailnet v2+ API") } - agentConn = NewWorkspaceAgentConn(conn, WorkspaceAgentConnOptions{ + agentConn = NewAgentConn(conn, AgentConnOptions{ AgentID: agentID, CloseFunc: func() error { cancel() @@ -405,10 +405,10 @@ type WorkspaceAgentReconnectingPTYOpts struct { SignedToken string } -// WorkspaceAgentReconnectingPTY spawns a PTY that reconnects using the token provided. +// 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 *WorkspaceClient) WorkspaceAgentReconnectingPTY(ctx context.Context, opts WorkspaceAgentReconnectingPTYOpts) (net.Conn, error) { +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) diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index 5ad76d153843b..4857f1e1a2e23 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -273,7 +273,7 @@ func TestAuditLogging(t *testing.T) { DontAddLicense: true, }) r := setupWorkspaceAgent(t, client, user, 0) - conn, err := workspacesdk.NewWorkspaceClient(client).DialWorkspaceAgent(ctx, r.sdkAgent.ID, nil) //nolint:gocritic // RBAC is not the purpose of this test + conn, err := workspacesdk.NewClient(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 d2c111e70099f..bdeb9466253f7 100644 --- a/enterprise/coderd/replicas_test.go +++ b/enterprise/coderd/replicas_test.go @@ -83,8 +83,8 @@ func TestReplicas(t *testing.T) { require.Len(t, replicas, 2) r := setupWorkspaceAgent(t, firstClient, firstUser, 0) - conn, err := workspacesdk.NewWorkspaceClient(secondClient). - DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(secondClient). + DialAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialAgentOptions{ BlockEndpoints: true, Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), }) @@ -130,8 +130,8 @@ func TestReplicas(t *testing.T) { require.Len(t, replicas, 2) r := setupWorkspaceAgent(t, firstClient, firstUser, 0) - conn, err := workspacesdk.NewWorkspaceClient(secondClient). - DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(secondClient). + DialAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialAgentOptions{ BlockEndpoints: true, Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), }) diff --git a/enterprise/coderd/workspaceagents_test.go b/enterprise/coderd/workspaceagents_test.go index eb6408e53b924..da9f45517500b 100644 --- a/enterprise/coderd/workspaceagents_test.go +++ b/enterprise/coderd/workspaceagents_test.go @@ -47,7 +47,7 @@ func TestBlockNonBrowser(t *testing.T) { }) r := setupWorkspaceAgent(t, client, user, 0) //nolint:gocritic // Testing that even the owner gets blocked. - _, err := workspacesdk.NewWorkspaceClient(client).DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, nil) + _, err := workspacesdk.NewClient(client).DialAgent(context.Background(), r.sdkAgent.ID, nil) var apiErr *codersdk.Error require.ErrorAs(t, err, &apiErr) require.Equal(t, http.StatusConflict, apiErr.StatusCode()) @@ -66,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 := workspacesdk.NewWorkspaceClient(client).DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, nil) + conn, err := workspacesdk.NewClient(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 33c4813821579..623772b63cb4d 100644 --- a/enterprise/tailnet/pgcoord_test.go +++ b/enterprise/tailnet/pgcoord_test.go @@ -228,7 +228,7 @@ func TestPGCoordinatorSingle_AgentValidIPLegacy(t *testing.T) { defer agent.close() agent.sendNode(&agpl.Node{ Addresses: []netip.Prefix{ - netip.PrefixFrom(workspacesdk.WorkspaceAgentIP, 128), + netip.PrefixFrom(workspacesdk.AgentIP, 128), }, PreferredDERP: 10, }) diff --git a/enterprise/wsproxy/wsproxy_test.go b/enterprise/wsproxy/wsproxy_test.go index 4b5133aa81615..de05a0e445c4f 100644 --- a/enterprise/wsproxy/wsproxy_test.go +++ b/enterprise/wsproxy/wsproxy_test.go @@ -197,7 +197,7 @@ resourceLoop: t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) - connInfo, err := workspacesdk.NewWorkspaceClient(client).WorkspaceAgentConnectionInfo(ctx, agentID) + connInfo, err := workspacesdk.NewClient(client).AgentConnectionInfo(ctx, agentID) require.NoError(t, err) // There should be three DERP regions in the map: the primary, and each @@ -270,7 +270,7 @@ resourceLoop: t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) - connInfo, err := workspacesdk.NewWorkspaceClient(client).WorkspaceAgentConnectionInfo(ctx, agentID) + connInfo, err := workspacesdk.NewClient(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())) @@ -430,8 +430,8 @@ resourceLoop: _ = coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Connect to the workspace agent. - conn, err := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, &slogtest.Options{ IgnoreErrors: true, }).Named("client").Leveled(slog.LevelDebug), diff --git a/enterprise/wsproxy/wsproxysdk/wsproxysdk.go b/enterprise/wsproxy/wsproxysdk/wsproxysdk.go index 84454b7b8eb9d..2ae2a6fc5906b 100644 --- a/enterprise/wsproxy/wsproxysdk/wsproxysdk.go +++ b/enterprise/wsproxy/wsproxysdk/wsproxysdk.go @@ -77,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 *workspacesdk.DialWorkspaceAgentOptions) (agentConn *workspacesdk.WorkspaceAgentConn, err error) { - return workspacesdk.NewWorkspaceClient(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.NewClient(c.SDKClient).DialAgent(ctx, agentID, options) } type IssueSignedAppTokenResponse struct { diff --git a/scaletest/agentconn/run.go b/scaletest/agentconn/run.go index 4e328a683e1bd..c85a0a3ddb243 100644 --- a/scaletest/agentconn/run.go +++ b/scaletest/agentconn/run.go @@ -63,8 +63,8 @@ func (r *Runner) Run(ctx context.Context, _ string, w io.Writer) error { _, _ = fmt.Fprintln(logs, "\tUsing proxied DERP connection through coder server...") } - conn, err := workspacesdk.NewWorkspaceClient(r.client). - DialWorkspaceAgent(ctx, r.cfg.AgentID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(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, @@ -133,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 *workspacesdk.WorkspaceAgentConn) error { +func waitForDisco(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error { const pingAttempts = 10 const pingDelay = 1 * time.Second @@ -165,7 +165,7 @@ func waitForDisco(ctx context.Context, logs io.Writer, conn *workspacesdk.Worksp return nil } -func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.WorkspaceAgentConn) error { +func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error { const directConnectionAttempts = 30 const directConnectionDelay = 1 * time.Second @@ -207,7 +207,7 @@ func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *workspac return nil } -func verifyConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.WorkspaceAgentConn) error { +func verifyConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error { const verifyConnectionAttempts = 30 const verifyConnectionDelay = 1 * time.Second @@ -221,7 +221,7 @@ func verifyConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.Wo u := &url.URL{ Scheme: "http", - Host: net.JoinHostPort("localhost", strconv.Itoa(workspacesdk.WorkspaceAgentHTTPAPIServerPort)), + Host: net.JoinHostPort("localhost", strconv.Itoa(workspacesdk.AgentHTTPAPIServerPort)), Path: "/", } req, err := http.NewRequestWithContext(verifyCtx, http.MethodGet, u.String(), nil) @@ -249,7 +249,7 @@ func verifyConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.Wo return nil } -func performInitialConnections(ctx context.Context, logs io.Writer, conn *workspacesdk.WorkspaceAgentConn, specs []Connection) error { +func performInitialConnections(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn, specs []Connection) error { if len(specs) == 0 { return nil } @@ -287,7 +287,7 @@ func performInitialConnections(ctx context.Context, logs io.Writer, conn *worksp return nil } -func holdConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.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() @@ -364,7 +364,7 @@ func holdConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.Work return nil } -func agentHTTPClient(conn *workspacesdk.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 e475e3a7d54d9..486391b7d0829 100644 --- a/scaletest/createworkspaces/run_test.go +++ b/scaletest/createworkspaces/run_test.go @@ -128,7 +128,7 @@ func Test_Runner(t *testing.T) { }, }, ReconnectingPTY: &reconnectingpty.Config{ - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Height: 24, Width: 80, Command: "echo hello", @@ -417,7 +417,7 @@ func Test_Runner(t *testing.T) { }, }, ReconnectingPTY: &reconnectingpty.Config{ - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Height: 24, Width: 80, Command: "echo hello", diff --git a/scaletest/reconnectingpty/config.go b/scaletest/reconnectingpty/config.go index a49f7f3d25e09..023f817499808 100644 --- a/scaletest/reconnectingpty/config.go +++ b/scaletest/reconnectingpty/config.go @@ -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 workspacesdk.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 07adfc8816c31..e0712b4631097 100644 --- a/scaletest/reconnectingpty/config_test.go +++ b/scaletest/reconnectingpty/config_test.go @@ -31,7 +31,7 @@ func Test_Config(t *testing.T) { name: "OKFull", config: reconnectingpty.Config{ AgentID: id, - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ ID: id, Width: 80, Height: 24, diff --git a/scaletest/reconnectingpty/run.go b/scaletest/reconnectingpty/run.go index 57060907d40f2..2ac62f6bdeab8 100644 --- a/scaletest/reconnectingpty/run.go +++ b/scaletest/reconnectingpty/run.go @@ -65,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 := workspacesdk.NewWorkspaceClient(r.client).WorkspaceAgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ + conn, err := workspacesdk.NewClient(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 b87a66af662cf..c740f922fa432 100644 --- a/scaletest/reconnectingpty/run_test.go +++ b/scaletest/reconnectingpty/run_test.go @@ -30,7 +30,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ // Use ; here because it's powershell compatible (vs &&). Command: "echo 'hello world'; sleep 1", }, @@ -59,7 +59,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'", }, LogOutput: false, @@ -87,7 +87,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'", }, Timeout: httpapi.Duration(2 * testutil.WaitSuperLong), @@ -111,7 +111,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "sleep 120", }, Timeout: httpapi.Duration(2 * time.Second), @@ -140,7 +140,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "sleep 120", }, Timeout: httpapi.Duration(2 * time.Second), @@ -165,7 +165,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'", }, Timeout: httpapi.Duration(2 * testutil.WaitSuperLong), @@ -195,7 +195,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: workspacesdk.WorkspaceAgentReconnectingPTYInit{ + Init: workspacesdk.AgentReconnectingPTYInit{ Command: "echo 'hello world'; sleep 1", }, ExpectOutput: "hello world", @@ -219,7 +219,7 @@ func Test_Runner(t *testing.T) { runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{ AgentID: agentID, - Init: workspacesdk.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 3edf14915ce3e..beb6a94d0c71b 100644 --- a/scaletest/workspacetraffic/conn.go +++ b/scaletest/workspacetraffic/conn.go @@ -39,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 := workspacesdk.NewWorkspaceClient(client).WorkspaceAgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ + conn, err := workspacesdk.NewClient(client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ AgentID: agentID, Reconnect: reconnect, Width: uint16(width), @@ -157,7 +157,7 @@ func connectSSH(ctx context.Context, client *codersdk.Client, agentID uuid.UUID, } }() - agentConn, err := workspacesdk.NewWorkspaceClient(client).DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{}) + agentConn, err := workspacesdk.NewClient(client).DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{}) if err != nil { return nil, xerrors.Errorf("dial workspace agent: %w", err) } diff --git a/support/support.go b/support/support.go index 294374a8cee54..3ec58cdd1982f 100644 --- a/support/support.go +++ b/support/support.go @@ -46,9 +46,9 @@ type Deployment struct { } type Network struct { - CoordinatorDebug string `json:"coordinator_debug"` - TailnetDebug string `json:"tailnet_debug"` - Netcheck *workspacesdk.WorkspaceAgentConnectionInfo `json:"netcheck"` + CoordinatorDebug string `json:"coordinator_debug"` + TailnetDebug string `json:"tailnet_debug"` + Netcheck *workspacesdk.AgentConnectionInfo `json:"netcheck"` } type Workspace struct { @@ -175,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 := workspacesdk.NewWorkspaceClient(client).WorkspaceAgentConnectionInfo(ctx, agentID) + connInfo, err := workspacesdk.NewClient(client).AgentConnectionInfo(ctx, agentID) if err != nil { return xerrors.Errorf("fetch agent conn info: %w", err) } @@ -333,8 +333,8 @@ 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 := workspacesdk.NewWorkspaceClient(client). - DialWorkspaceAgent(ctx, agentID, &workspacesdk.DialWorkspaceAgentOptions{ + conn, err := workspacesdk.NewClient(client). + DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ Logger: log.Named("dial-agent"), BlockEndpoints: false, }) diff --git a/tailnet/coordinator_test.go b/tailnet/coordinator_test.go index c1e06314eff8a..d8a6f297b539d 100644 --- a/tailnet/coordinator_test.go +++ b/tailnet/coordinator_test.go @@ -155,7 +155,7 @@ func TestCoordinator(t *testing.T) { }() sendNode(&tailnet.Node{ Addresses: []netip.Prefix{ - netip.PrefixFrom(workspacesdk.WorkspaceAgentIP, 128), + netip.PrefixFrom(workspacesdk.AgentIP, 128), }, PreferredDERP: 10, }) From a5e7b2e8a8a0ecc0bdb853b5d183a5013ba7e917 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 25 Mar 2024 20:12:08 +0000 Subject: [PATCH 07/11] remove health prefixes from `healthsdk` --- coderd/database/types.go | 4 +- coderd/debug.go | 28 ++++++------- coderd/debug_test.go | 34 +++++++-------- coderd/healthcheck/healthcheck.go | 14 +++---- coderd/healthcheck/healthcheck_test.go | 36 ++++++++-------- codersdk/healthsdk/health.go | 58 +++++++++++++------------- support/support.go | 2 +- 7 files changed, 88 insertions(+), 88 deletions(-) diff --git a/coderd/database/types.go b/coderd/database/types.go index b9f195e541c20..baf4061e50950 100644 --- a/coderd/database/types.go +++ b/coderd/database/types.go @@ -25,8 +25,8 @@ type AuditOAuthConvertState struct { } type HealthSettings struct { - ID uuid.UUID `db:"id" json:"id"` - DismissedHealthchecks []healthsdk.HealthSection `db:"dismissed_healthchecks" json:"dismissed_healthchecks"` + ID uuid.UUID `db:"id" json:"id"` + DismissedHealthchecks []healthsdk.Section `db:"dismissed_healthchecks" json:"dismissed_healthchecks"` } type Actions []rbac.Action diff --git a/coderd/debug.go b/coderd/debug.go index d97edfe0b73fe..3eaac953f1a9d 100644 --- a/coderd/debug.go +++ b/coderd/debug.go @@ -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 healthsdk.HealthcheckReport, dismissed ...healthsdk.HealthSection) { +func formatHealthcheck(ctx context.Context, rw http.ResponseWriter, r *http.Request, hc healthsdk.HealthcheckReport, dismissed ...healthsdk.Section) { // Mark any sections previously marked as dismissed. for _, d := range dismissed { switch d { - case healthsdk.HealthSectionAccessURL: + case healthsdk.SectionAccessURL: hc.AccessURL.Dismissed = true - case healthsdk.HealthSectionDERP: + case healthsdk.SectionDERP: hc.DERP.Dismissed = true - case healthsdk.HealthSectionDatabase: + case healthsdk.SectionDatabase: hc.Database.Dismissed = true - case healthsdk.HealthSectionWebsocket: + case healthsdk.SectionWebsocket: hc.Websocket.Dismissed = true - case healthsdk.HealthSectionWorkspaceProxy: + case healthsdk.SectionWorkspaceProxy: hc.WorkspaceProxy.Dismissed = true } } @@ -164,7 +164,7 @@ func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request return } - var settings healthsdk.HealthSettings + var settings healthsdk.Settings 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 = []healthsdk.HealthSection{} + settings.DismissedHealthchecks = []healthsdk.Section{} } httpapi.Write(r.Context(), rw, http.StatusOK, settings) @@ -200,7 +200,7 @@ func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Requ return } - var settings healthsdk.HealthSettings + var settings healthsdk.Settings 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 healthsdk.HealthSettings) error { +func validateHealthSettings(settings healthsdk.Settings) error { for _, dismissed := range settings.DismissedHealthchecks { - ok := slices.Contains(healthsdk.HealthSections, dismissed) + ok := slices.Contains(healthsdk.Sections, 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) []healthsdk.HealthSection { - dismissedHealthchecks := []healthsdk.HealthSection{} +func loadDismissedHealthchecks(ctx context.Context, db database.Store, logger slog.Logger) []healthsdk.Section { + dismissedHealthchecks := []healthsdk.Section{} settingsJSON, err := db.GetHealthSettings(ctx) if err == nil { - var settings healthsdk.HealthSettings + var settings healthsdk.Settings 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 07b2bacf8de99..6883768fa5db4 100644 --- a/coderd/debug_test.go +++ b/coderd/debug_test.go @@ -251,11 +251,11 @@ func TestHealthSettings(t *testing.T) { _ = coderdtest.CreateFirstUser(t, adminClient) // when - settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) + settings, err := healthsdk.NewClient(adminClient).Settings(ctx) require.NoError(t, err) // then - require.Equal(t, healthsdk.HealthSettings{DismissedHealthchecks: []healthsdk.HealthSection{}}, settings) + require.Equal(t, healthsdk.Settings{DismissedHealthchecks: []healthsdk.Section{}}, 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 := healthsdk.HealthSettings{ - DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, + expected := healthsdk.Settings{ + DismissedHealthchecks: []healthsdk.Section{healthsdk.SectionDERP, healthsdk.SectionWebsocket}, } // when: dismiss "derp" and "websocket" - err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) + err := healthsdk.NewClient(adminClient).PutSettings(ctx, expected) require.NoError(t, err) // then - settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) + settings, err := healthsdk.NewClient(adminClient).Settings(ctx) require.NoError(t, err) require.Equal(t, expected, settings) @@ -303,23 +303,23 @@ func TestHealthSettings(t *testing.T) { adminClient := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, adminClient) - initial := healthsdk.HealthSettings{ - DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, + initial := healthsdk.Settings{ + DismissedHealthchecks: []healthsdk.Section{healthsdk.SectionDERP, healthsdk.SectionWebsocket}, } - err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, initial) + err := healthsdk.NewClient(adminClient).PutSettings(ctx, initial) require.NoError(t, err) - expected := healthsdk.HealthSettings{ - DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP}, + expected := healthsdk.Settings{ + DismissedHealthchecks: []healthsdk.Section{healthsdk.SectionDERP}, } // when: undismiss "websocket" - err = healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) + err = healthsdk.NewClient(adminClient).PutSettings(ctx, expected) require.NoError(t, err) // then - settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) + settings, err := healthsdk.NewClient(adminClient).Settings(ctx) require.NoError(t, err) require.Equal(t, expected, settings) @@ -345,15 +345,15 @@ func TestHealthSettings(t *testing.T) { adminClient := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, adminClient) - expected := healthsdk.HealthSettings{ - DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, + expected := healthsdk.Settings{ + DismissedHealthchecks: []healthsdk.Section{healthsdk.SectionDERP, healthsdk.SectionWebsocket}, } - err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) + err := healthsdk.NewClient(adminClient).PutSettings(ctx, expected) require.NoError(t, err) // when - err = healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) + err = healthsdk.NewClient(adminClient).PutSettings(ctx, expected) // then require.Error(t, err) diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index c724347721335..b7964a3125dbf 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -156,24 +156,24 @@ func Run(ctx context.Context, opts *ReportOptions) *healthsdk.HealthcheckReport wg.Wait() report.Time = time.Now() - report.FailingSections = []healthsdk.HealthSection{} + report.FailingSections = []healthsdk.Section{} if report.DERP.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionDERP) + report.FailingSections = append(report.FailingSections, healthsdk.SectionDERP) } if report.AccessURL.Severity.Value() > health.SeverityOK.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionAccessURL) + report.FailingSections = append(report.FailingSections, healthsdk.SectionAccessURL) } if report.Websocket.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionWebsocket) + report.FailingSections = append(report.FailingSections, healthsdk.SectionWebsocket) } if report.Database.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionDatabase) + report.FailingSections = append(report.FailingSections, healthsdk.SectionDatabase) } if report.WorkspaceProxy.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionWorkspaceProxy) + report.FailingSections = append(report.FailingSections, healthsdk.SectionWorkspaceProxy) } if report.ProvisionerDaemons.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionProvisionerDaemons) + report.FailingSections = append(report.FailingSections, healthsdk.SectionProvisionerDaemons) } report.Healthy = len(report.FailingSections) == 0 diff --git a/coderd/healthcheck/healthcheck_test.go b/coderd/healthcheck/healthcheck_test.go index bb5cd581dbf9c..164ae55675a47 100644 --- a/coderd/healthcheck/healthcheck_test.go +++ b/coderd/healthcheck/healthcheck_test.go @@ -53,7 +53,7 @@ func TestHealthcheck(t *testing.T) { checker *testChecker healthy bool severity health.Severity - failingSections []healthsdk.HealthSection + failingSections []healthsdk.Section }{{ name: "OK", checker: &testChecker{ @@ -83,7 +83,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: true, severity: health.SeverityOK, - failingSections: []healthsdk.HealthSection{}, + failingSections: []healthsdk.Section{}, }, { name: "DERPFail", checker: &testChecker{ @@ -113,7 +113,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: false, severity: health.SeverityError, - failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionDERP}, + failingSections: []healthsdk.Section{healthsdk.SectionDERP}, }, { name: "DERPWarning", checker: &testChecker{ @@ -144,7 +144,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: true, severity: health.SeverityWarning, - failingSections: []healthsdk.HealthSection{}, + failingSections: []healthsdk.Section{}, }, { name: "AccessURLFail", checker: &testChecker{ @@ -174,7 +174,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: false, severity: health.SeverityWarning, - failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionAccessURL}, + failingSections: []healthsdk.Section{healthsdk.SectionAccessURL}, }, { name: "WebsocketFail", checker: &testChecker{ @@ -204,7 +204,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: false, severity: health.SeverityError, - failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionWebsocket}, + failingSections: []healthsdk.Section{healthsdk.SectionWebsocket}, }, { name: "DatabaseFail", checker: &testChecker{ @@ -234,7 +234,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: false, severity: health.SeverityError, - failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionDatabase}, + failingSections: []healthsdk.Section{healthsdk.SectionDatabase}, }, { name: "ProxyFail", checker: &testChecker{ @@ -264,7 +264,7 @@ func TestHealthcheck(t *testing.T) { }, severity: health.SeverityError, healthy: false, - failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionWorkspaceProxy}, + failingSections: []healthsdk.Section{healthsdk.SectionWorkspaceProxy}, }, { name: "ProxyWarn", checker: &testChecker{ @@ -295,7 +295,7 @@ func TestHealthcheck(t *testing.T) { }, severity: health.SeverityWarning, healthy: true, - failingSections: []healthsdk.HealthSection{}, + failingSections: []healthsdk.Section{}, }, { name: "ProvisionerDaemonsFail", checker: &testChecker{ @@ -325,7 +325,7 @@ func TestHealthcheck(t *testing.T) { }, severity: health.SeverityError, healthy: false, - failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionProvisionerDaemons}, + failingSections: []healthsdk.Section{healthsdk.SectionProvisionerDaemons}, }, { name: "ProvisionerDaemonsWarn", checker: &testChecker{ @@ -356,7 +356,7 @@ func TestHealthcheck(t *testing.T) { }, severity: health.SeverityWarning, healthy: true, - failingSections: []healthsdk.HealthSection{}, + failingSections: []healthsdk.Section{}, }, { name: "AllFail", healthy: false, @@ -386,13 +386,13 @@ func TestHealthcheck(t *testing.T) { }, }, severity: health.SeverityError, - failingSections: []healthsdk.HealthSection{ - healthsdk.HealthSectionDERP, - healthsdk.HealthSectionAccessURL, - healthsdk.HealthSectionWebsocket, - healthsdk.HealthSectionDatabase, - healthsdk.HealthSectionWorkspaceProxy, - healthsdk.HealthSectionProvisionerDaemons, + failingSections: []healthsdk.Section{ + healthsdk.SectionDERP, + healthsdk.SectionAccessURL, + healthsdk.SectionWebsocket, + healthsdk.SectionDatabase, + healthsdk.SectionWorkspaceProxy, + healthsdk.SectionProvisionerDaemons, }, }} { c := c diff --git a/codersdk/healthsdk/health.go b/codersdk/healthsdk/health.go index eb728b6531d46..d18ce9f8fc9d6 100644 --- a/codersdk/healthsdk/health.go +++ b/codersdk/healthsdk/health.go @@ -15,45 +15,45 @@ import ( "github.com/coder/coder/v2/codersdk" ) -// @typescript-ignore HealthClient -type HealthClient struct { +// @typescript-ignore Client +type Client struct { client *codersdk.Client } -func NewHealthClient(c *codersdk.Client) *HealthClient { - return &HealthClient{client: c} +func NewClient(c *codersdk.Client) *Client { + return &Client{client: c} } -type HealthSection string +type Section string // If you add another const below, make sure to add it to HealthSections! const ( - HealthSectionDERP HealthSection = "DERP" - HealthSectionAccessURL HealthSection = "AccessURL" - HealthSectionWebsocket HealthSection = "Websocket" - HealthSectionDatabase HealthSection = "Database" - HealthSectionWorkspaceProxy HealthSection = "WorkspaceProxy" - HealthSectionProvisionerDaemons HealthSection = "ProvisionerDaemons" + SectionDERP Section = "DERP" + SectionAccessURL Section = "AccessURL" + SectionWebsocket Section = "Websocket" + SectionDatabase Section = "Database" + SectionWorkspaceProxy Section = "WorkspaceProxy" + SectionProvisionerDaemons Section = "ProvisionerDaemons" ) -var HealthSections = []HealthSection{ - HealthSectionDERP, - HealthSectionAccessURL, - HealthSectionWebsocket, - HealthSectionDatabase, - HealthSectionWorkspaceProxy, - HealthSectionProvisionerDaemons, +var Sections = []Section{ + SectionDERP, + SectionAccessURL, + SectionWebsocket, + SectionDatabase, + SectionWorkspaceProxy, + SectionProvisionerDaemons, } -type HealthSettings struct { - DismissedHealthchecks []HealthSection `json:"dismissed_healthchecks"` +type Settings struct { + DismissedHealthchecks []Section `json:"dismissed_healthchecks"` } -type UpdateHealthSettings struct { - DismissedHealthchecks []HealthSection `json:"dismissed_healthchecks"` +type UpdateSettings struct { + DismissedHealthchecks []Section `json:"dismissed_healthchecks"` } -func (c *HealthClient) DebugHealth(ctx context.Context) (HealthcheckReport, error) { +func (c *Client) 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 @@ -66,20 +66,20 @@ func (c *HealthClient) DebugHealth(ctx context.Context) (HealthcheckReport, erro return rpt, json.NewDecoder(res.Body).Decode(&rpt) } -func (c *HealthClient) HealthSettings(ctx context.Context) (HealthSettings, error) { +func (c *Client) Settings(ctx context.Context) (Settings, error) { res, err := c.client.Request(ctx, http.MethodGet, "/api/v2/debug/health/settings", nil) if err != nil { - return HealthSettings{}, err + return Settings{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return HealthSettings{}, codersdk.ReadBodyAsError(res) + return Settings{}, codersdk.ReadBodyAsError(res) } - var settings HealthSettings + var settings Settings return settings, json.NewDecoder(res.Body).Decode(&settings) } -func (c *HealthClient) PutHealthSettings(ctx context.Context, settings HealthSettings) error { +func (c *Client) PutSettings(ctx context.Context, settings Settings) error { res, err := c.client.Request(ctx, http.MethodPut, "/api/v2/debug/health/settings", settings) if err != nil { return err @@ -104,7 +104,7 @@ type HealthcheckReport struct { // Severity indicates the status of Coder health. Severity health.Severity `json:"severity" enums:"ok,warning,error"` // FailingSections is a list of sections that have failed their healthcheck. - FailingSections []HealthSection `json:"failing_sections"` + FailingSections []Section `json:"failing_sections"` DERP DERPHealthReport `json:"derp"` AccessURL AccessURLReport `json:"access_url"` diff --git a/support/support.go b/support/support.go index 3ec58cdd1982f..24645adffb8be 100644 --- a/support/support.go +++ b/support/support.go @@ -112,7 +112,7 @@ func DeploymentInfo(ctx context.Context, client *codersdk.Client, log slog.Logge }) eg.Go(func() error { - hr, err := healthsdk.NewHealthClient(client).DebugHealth(ctx) + hr, err := healthsdk.NewClient(client).DebugHealth(ctx) if err != nil { return xerrors.Errorf("fetch health report: %w", err) } From 57a645ca66e1a5204938a3f4fa9e483a460c6c03 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 25 Mar 2024 20:32:37 +0000 Subject: [PATCH 08/11] Revert "remove health prefixes from `healthsdk`" This reverts commit a5e7b2e8a8a0ecc0bdb853b5d183a5013ba7e917. --- coderd/database/types.go | 4 +- coderd/debug.go | 28 ++++++------- coderd/debug_test.go | 34 +++++++-------- coderd/healthcheck/healthcheck.go | 14 +++---- coderd/healthcheck/healthcheck_test.go | 36 ++++++++-------- codersdk/healthsdk/health.go | 58 +++++++++++++------------- support/support.go | 2 +- 7 files changed, 88 insertions(+), 88 deletions(-) diff --git a/coderd/database/types.go b/coderd/database/types.go index baf4061e50950..b9f195e541c20 100644 --- a/coderd/database/types.go +++ b/coderd/database/types.go @@ -25,8 +25,8 @@ type AuditOAuthConvertState struct { } type HealthSettings struct { - ID uuid.UUID `db:"id" json:"id"` - DismissedHealthchecks []healthsdk.Section `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 3eaac953f1a9d..d97edfe0b73fe 100644 --- a/coderd/debug.go +++ b/coderd/debug.go @@ -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 healthsdk.HealthcheckReport, dismissed ...healthsdk.Section) { +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 healthsdk.SectionAccessURL: + case healthsdk.HealthSectionAccessURL: hc.AccessURL.Dismissed = true - case healthsdk.SectionDERP: + case healthsdk.HealthSectionDERP: hc.DERP.Dismissed = true - case healthsdk.SectionDatabase: + case healthsdk.HealthSectionDatabase: hc.Database.Dismissed = true - case healthsdk.SectionWebsocket: + case healthsdk.HealthSectionWebsocket: hc.Websocket.Dismissed = true - case healthsdk.SectionWorkspaceProxy: + case healthsdk.HealthSectionWorkspaceProxy: hc.WorkspaceProxy.Dismissed = true } } @@ -164,7 +164,7 @@ func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request return } - var settings healthsdk.Settings + 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 = []healthsdk.Section{} + settings.DismissedHealthchecks = []healthsdk.HealthSection{} } httpapi.Write(r.Context(), rw, http.StatusOK, settings) @@ -200,7 +200,7 @@ func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Requ return } - var settings healthsdk.Settings + 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 healthsdk.Settings) error { +func validateHealthSettings(settings healthsdk.HealthSettings) error { for _, dismissed := range settings.DismissedHealthchecks { - ok := slices.Contains(healthsdk.Sections, 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) []healthsdk.Section { - dismissedHealthchecks := []healthsdk.Section{} +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 healthsdk.Settings + 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 6883768fa5db4..07b2bacf8de99 100644 --- a/coderd/debug_test.go +++ b/coderd/debug_test.go @@ -251,11 +251,11 @@ func TestHealthSettings(t *testing.T) { _ = coderdtest.CreateFirstUser(t, adminClient) // when - settings, err := healthsdk.NewClient(adminClient).Settings(ctx) + settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) require.NoError(t, err) // then - require.Equal(t, healthsdk.Settings{DismissedHealthchecks: []healthsdk.Section{}}, 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 := healthsdk.Settings{ - DismissedHealthchecks: []healthsdk.Section{healthsdk.SectionDERP, healthsdk.SectionWebsocket}, + expected := healthsdk.HealthSettings{ + DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, } // when: dismiss "derp" and "websocket" - err := healthsdk.NewClient(adminClient).PutSettings(ctx, expected) + err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // then - settings, err := healthsdk.NewClient(adminClient).Settings(ctx) + settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) require.NoError(t, err) require.Equal(t, expected, settings) @@ -303,23 +303,23 @@ func TestHealthSettings(t *testing.T) { adminClient := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, adminClient) - initial := healthsdk.Settings{ - DismissedHealthchecks: []healthsdk.Section{healthsdk.SectionDERP, healthsdk.SectionWebsocket}, + initial := healthsdk.HealthSettings{ + DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, } - err := healthsdk.NewClient(adminClient).PutSettings(ctx, initial) + err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, initial) require.NoError(t, err) - expected := healthsdk.Settings{ - DismissedHealthchecks: []healthsdk.Section{healthsdk.SectionDERP}, + expected := healthsdk.HealthSettings{ + DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP}, } // when: undismiss "websocket" - err = healthsdk.NewClient(adminClient).PutSettings(ctx, expected) + err = healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // then - settings, err := healthsdk.NewClient(adminClient).Settings(ctx) + settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) require.NoError(t, err) require.Equal(t, expected, settings) @@ -345,15 +345,15 @@ func TestHealthSettings(t *testing.T) { adminClient := coderdtest.New(t, nil) _ = coderdtest.CreateFirstUser(t, adminClient) - expected := healthsdk.Settings{ - DismissedHealthchecks: []healthsdk.Section{healthsdk.SectionDERP, healthsdk.SectionWebsocket}, + expected := healthsdk.HealthSettings{ + DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, } - err := healthsdk.NewClient(adminClient).PutSettings(ctx, expected) + err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // when - err = healthsdk.NewClient(adminClient).PutSettings(ctx, expected) + err = healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) // then require.Error(t, err) diff --git a/coderd/healthcheck/healthcheck.go b/coderd/healthcheck/healthcheck.go index b7964a3125dbf..c724347721335 100644 --- a/coderd/healthcheck/healthcheck.go +++ b/coderd/healthcheck/healthcheck.go @@ -156,24 +156,24 @@ func Run(ctx context.Context, opts *ReportOptions) *healthsdk.HealthcheckReport wg.Wait() report.Time = time.Now() - report.FailingSections = []healthsdk.Section{} + report.FailingSections = []healthsdk.HealthSection{} if report.DERP.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.SectionDERP) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionDERP) } if report.AccessURL.Severity.Value() > health.SeverityOK.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.SectionAccessURL) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionAccessURL) } if report.Websocket.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.SectionWebsocket) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionWebsocket) } if report.Database.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.SectionDatabase) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionDatabase) } if report.WorkspaceProxy.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.SectionWorkspaceProxy) + report.FailingSections = append(report.FailingSections, healthsdk.HealthSectionWorkspaceProxy) } if report.ProvisionerDaemons.Severity.Value() > health.SeverityWarning.Value() { - report.FailingSections = append(report.FailingSections, healthsdk.SectionProvisionerDaemons) + 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 164ae55675a47..bb5cd581dbf9c 100644 --- a/coderd/healthcheck/healthcheck_test.go +++ b/coderd/healthcheck/healthcheck_test.go @@ -53,7 +53,7 @@ func TestHealthcheck(t *testing.T) { checker *testChecker healthy bool severity health.Severity - failingSections []healthsdk.Section + failingSections []healthsdk.HealthSection }{{ name: "OK", checker: &testChecker{ @@ -83,7 +83,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: true, severity: health.SeverityOK, - failingSections: []healthsdk.Section{}, + failingSections: []healthsdk.HealthSection{}, }, { name: "DERPFail", checker: &testChecker{ @@ -113,7 +113,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: false, severity: health.SeverityError, - failingSections: []healthsdk.Section{healthsdk.SectionDERP}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionDERP}, }, { name: "DERPWarning", checker: &testChecker{ @@ -144,7 +144,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: true, severity: health.SeverityWarning, - failingSections: []healthsdk.Section{}, + failingSections: []healthsdk.HealthSection{}, }, { name: "AccessURLFail", checker: &testChecker{ @@ -174,7 +174,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: false, severity: health.SeverityWarning, - failingSections: []healthsdk.Section{healthsdk.SectionAccessURL}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionAccessURL}, }, { name: "WebsocketFail", checker: &testChecker{ @@ -204,7 +204,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: false, severity: health.SeverityError, - failingSections: []healthsdk.Section{healthsdk.SectionWebsocket}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionWebsocket}, }, { name: "DatabaseFail", checker: &testChecker{ @@ -234,7 +234,7 @@ func TestHealthcheck(t *testing.T) { }, healthy: false, severity: health.SeverityError, - failingSections: []healthsdk.Section{healthsdk.SectionDatabase}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionDatabase}, }, { name: "ProxyFail", checker: &testChecker{ @@ -264,7 +264,7 @@ func TestHealthcheck(t *testing.T) { }, severity: health.SeverityError, healthy: false, - failingSections: []healthsdk.Section{healthsdk.SectionWorkspaceProxy}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionWorkspaceProxy}, }, { name: "ProxyWarn", checker: &testChecker{ @@ -295,7 +295,7 @@ func TestHealthcheck(t *testing.T) { }, severity: health.SeverityWarning, healthy: true, - failingSections: []healthsdk.Section{}, + failingSections: []healthsdk.HealthSection{}, }, { name: "ProvisionerDaemonsFail", checker: &testChecker{ @@ -325,7 +325,7 @@ func TestHealthcheck(t *testing.T) { }, severity: health.SeverityError, healthy: false, - failingSections: []healthsdk.Section{healthsdk.SectionProvisionerDaemons}, + failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionProvisionerDaemons}, }, { name: "ProvisionerDaemonsWarn", checker: &testChecker{ @@ -356,7 +356,7 @@ func TestHealthcheck(t *testing.T) { }, severity: health.SeverityWarning, healthy: true, - failingSections: []healthsdk.Section{}, + failingSections: []healthsdk.HealthSection{}, }, { name: "AllFail", healthy: false, @@ -386,13 +386,13 @@ func TestHealthcheck(t *testing.T) { }, }, severity: health.SeverityError, - failingSections: []healthsdk.Section{ - healthsdk.SectionDERP, - healthsdk.SectionAccessURL, - healthsdk.SectionWebsocket, - healthsdk.SectionDatabase, - healthsdk.SectionWorkspaceProxy, - healthsdk.SectionProvisionerDaemons, + failingSections: []healthsdk.HealthSection{ + healthsdk.HealthSectionDERP, + healthsdk.HealthSectionAccessURL, + healthsdk.HealthSectionWebsocket, + healthsdk.HealthSectionDatabase, + healthsdk.HealthSectionWorkspaceProxy, + healthsdk.HealthSectionProvisionerDaemons, }, }} { c := c diff --git a/codersdk/healthsdk/health.go b/codersdk/healthsdk/health.go index d18ce9f8fc9d6..eb728b6531d46 100644 --- a/codersdk/healthsdk/health.go +++ b/codersdk/healthsdk/health.go @@ -15,45 +15,45 @@ import ( "github.com/coder/coder/v2/codersdk" ) -// @typescript-ignore Client -type Client struct { +// @typescript-ignore HealthClient +type HealthClient struct { client *codersdk.Client } -func NewClient(c *codersdk.Client) *Client { - return &Client{client: c} +func NewHealthClient(c *codersdk.Client) *HealthClient { + return &HealthClient{client: c} } -type Section string +type HealthSection string // If you add another const below, make sure to add it to HealthSections! const ( - SectionDERP Section = "DERP" - SectionAccessURL Section = "AccessURL" - SectionWebsocket Section = "Websocket" - SectionDatabase Section = "Database" - SectionWorkspaceProxy Section = "WorkspaceProxy" - SectionProvisionerDaemons Section = "ProvisionerDaemons" + HealthSectionDERP HealthSection = "DERP" + HealthSectionAccessURL HealthSection = "AccessURL" + HealthSectionWebsocket HealthSection = "Websocket" + HealthSectionDatabase HealthSection = "Database" + HealthSectionWorkspaceProxy HealthSection = "WorkspaceProxy" + HealthSectionProvisionerDaemons HealthSection = "ProvisionerDaemons" ) -var Sections = []Section{ - SectionDERP, - SectionAccessURL, - SectionWebsocket, - SectionDatabase, - SectionWorkspaceProxy, - SectionProvisionerDaemons, +var HealthSections = []HealthSection{ + HealthSectionDERP, + HealthSectionAccessURL, + HealthSectionWebsocket, + HealthSectionDatabase, + HealthSectionWorkspaceProxy, + HealthSectionProvisionerDaemons, } -type Settings struct { - DismissedHealthchecks []Section `json:"dismissed_healthchecks"` +type HealthSettings struct { + DismissedHealthchecks []HealthSection `json:"dismissed_healthchecks"` } -type UpdateSettings struct { - DismissedHealthchecks []Section `json:"dismissed_healthchecks"` +type UpdateHealthSettings struct { + DismissedHealthchecks []HealthSection `json:"dismissed_healthchecks"` } -func (c *Client) DebugHealth(ctx context.Context) (HealthcheckReport, error) { +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 @@ -66,20 +66,20 @@ func (c *Client) DebugHealth(ctx context.Context) (HealthcheckReport, error) { return rpt, json.NewDecoder(res.Body).Decode(&rpt) } -func (c *Client) Settings(ctx context.Context) (Settings, error) { +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 Settings{}, err + return HealthSettings{}, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return Settings{}, codersdk.ReadBodyAsError(res) + return HealthSettings{}, codersdk.ReadBodyAsError(res) } - var settings Settings + var settings HealthSettings return settings, json.NewDecoder(res.Body).Decode(&settings) } -func (c *Client) PutSettings(ctx context.Context, settings Settings) error { +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 @@ -104,7 +104,7 @@ type HealthcheckReport struct { // Severity indicates the status of Coder health. Severity health.Severity `json:"severity" enums:"ok,warning,error"` // FailingSections is a list of sections that have failed their healthcheck. - FailingSections []Section `json:"failing_sections"` + FailingSections []HealthSection `json:"failing_sections"` DERP DERPHealthReport `json:"derp"` AccessURL AccessURLReport `json:"access_url"` diff --git a/support/support.go b/support/support.go index 24645adffb8be..3ec58cdd1982f 100644 --- a/support/support.go +++ b/support/support.go @@ -112,7 +112,7 @@ func DeploymentInfo(ctx context.Context, client *codersdk.Client, log slog.Logge }) eg.Go(func() error { - hr, err := healthsdk.NewClient(client).DebugHealth(ctx) + hr, err := healthsdk.NewHealthClient(client).DebugHealth(ctx) if err != nil { return xerrors.Errorf("fetch health report: %w", err) } From 6cf909ea849cf073b137ea8d3705763d98f31915 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 25 Mar 2024 20:35:47 +0000 Subject: [PATCH 09/11] make gen --- coderd/apidoc/docs.go | 6 +++--- coderd/apidoc/swagger.json | 6 +++--- coderd/workspaceagents.go | 4 ++-- docs/api/agents.md | 6 +++--- docs/api/schemas.md | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 1c80ab3a3a036..a082996d3298c 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5479,7 +5479,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/workspacesdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.AgentConnectionInfo" } } }, @@ -6088,7 +6088,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/workspacesdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.AgentConnectionInfo" } } } @@ -14476,7 +14476,7 @@ const docTemplate = `{ } } }, - "workspacesdk.WorkspaceAgentConnectionInfo": { + "workspacesdk.AgentConnectionInfo": { "type": "object", "properties": { "derp_force_websockets": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 25cef3afafcc6..a559938463a2b 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -4830,7 +4830,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/workspacesdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.AgentConnectionInfo" } } }, @@ -5363,7 +5363,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/workspacesdk.WorkspaceAgentConnectionInfo" + "$ref": "#/definitions/workspacesdk.AgentConnectionInfo" } } } @@ -13202,7 +13202,7 @@ } } }, - "workspacesdk.WorkspaceAgentConnectionInfo": { + "workspacesdk.AgentConnectionInfo": { "type": "object", "properties": { "derp_force_websockets": { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 020bbc1c9cb83..34170b3bf7097 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -826,7 +826,7 @@ 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} workspacesdk.WorkspaceAgentConnectionInfo +// @Success 200 {object} workspacesdk.AgentConnectionInfo // @Router /workspaceagents/{workspaceagent}/connection [get] func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -846,7 +846,7 @@ func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request // @Security CoderSessionToken // @Produce json // @Tags Agents -// @Success 200 {object} workspacesdk.WorkspaceAgentConnectionInfo +// @Success 200 {object} workspacesdk.AgentConnectionInfo // @Router /workspaceagents/connection [get] // @x-apidocgen {"skip": true} func (api *API) workspaceAgentConnectionGeneric(rw http.ResponseWriter, r *http.Request) { diff --git a/docs/api/agents.md b/docs/api/agents.md index 938180f805cca..0d73ca9262c11 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 | [workspacesdk.WorkspaceAgentConnectionInfo](schemas.md#workspacesdkworkspaceagentconnectioninfo) | +| 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/schemas.md b/docs/api/schemas.md index 64aa47fcc29e0..f46e02a636026 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -9029,7 +9029,7 @@ _None_ | `user_id` | string | false | | | | `workspace_id` | string | false | | | -## workspacesdk.WorkspaceAgentConnectionInfo +## workspacesdk.AgentConnectionInfo ```json { From d107b9d604581ce28df44e5a3348d137a7665441 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 26 Mar 2024 17:21:44 +0000 Subject: [PATCH 10/11] rearrange --- cli/agent_test.go | 6 +- cli/configssh_test.go | 2 +- cli/netcheck.go | 2 +- cli/ping.go | 2 +- cli/portforward.go | 2 +- cli/speedtest.go | 2 +- cli/ssh.go | 2 +- cli/vscodessh.go | 2 +- coderd/activitybump_test.go | 4 +- coderd/coderd_test.go | 2 +- coderd/debug_test.go | 16 +- coderd/insights_test.go | 6 +- coderd/templates_test.go | 2 +- coderd/workspaceagents_test.go | 10 +- coderd/workspaceapps/apptest/apptest.go | 2 +- .../healthsdk/{health.go => healthsdk.go} | 2 +- .../{workspaceagentconn.go => agentconn.go} | 104 ------ .../{workspaceagents.go => connector.go} | 221 ------------ codersdk/workspacesdk/workspacesdk.go | 341 ++++++++++++++++++ ..._test.go => workspacesdk_internal_test.go} | 0 ...aceagents_test.go => workspacesdk_test.go} | 0 enterprise/coderd/coderd_test.go | 2 +- enterprise/coderd/replicas_test.go | 4 +- enterprise/coderd/workspaceagents_test.go | 4 +- enterprise/wsproxy/wsproxy_test.go | 6 +- enterprise/wsproxy/wsproxysdk/wsproxysdk.go | 2 +- scaletest/agentconn/run.go | 2 +- scaletest/reconnectingpty/run.go | 2 +- scaletest/workspacetraffic/conn.go | 4 +- support/support.go | 6 +- 30 files changed, 389 insertions(+), 373 deletions(-) rename codersdk/healthsdk/{health.go => healthsdk.go} (99%) rename codersdk/workspacesdk/{workspaceagentconn.go => agentconn.go} (81%) rename codersdk/workspacesdk/{workspaceagents.go => connector.go} (51%) create mode 100644 codersdk/workspacesdk/workspacesdk.go rename codersdk/workspacesdk/{workspaceagents_internal_test.go => workspacesdk_internal_test.go} (100%) rename codersdk/workspacesdk/{workspaceagents_test.go => workspacesdk_test.go} (100%) diff --git a/cli/agent_test.go b/cli/agent_test.go index bc283d272f8e9..9571bf03e1a09 100644 --- a/cli/agent_test.go +++ b/cli/agent_test.go @@ -92,7 +92,7 @@ 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 := workspacesdk.NewClient(client). + dialer, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer dialer.Close() @@ -132,7 +132,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 := workspacesdk.NewClient(client). + dialer, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer dialer.Close() @@ -176,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 := workspacesdk.NewClient(client).DialAgent(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 1a0399bde60d0..f1be8abe8b4b9 100644 --- a/cli/configssh_test.go +++ b/cli/configssh_test.go @@ -84,7 +84,7 @@ func TestConfigSSH(t *testing.T) { }).WithAgent().Do() _ = agenttest.New(t, client.URL, r.AgentToken) resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID) - agentConn, err := workspacesdk.NewClient(client). + 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/netcheck.go b/cli/netcheck.go index 9823a0deafd89..fb4042b600920 100644 --- a/cli/netcheck.go +++ b/cli/netcheck.go @@ -27,7 +27,7 @@ func (r *RootCmd) netcheck() *serpent.Command { ctx, cancel := context.WithTimeout(inv.Context(), 30*time.Second) defer cancel() - connInfo, err := workspacesdk.NewClient(client).AgentConnectionInfoGeneric(ctx) + connInfo, err := workspacesdk.New(client).AgentConnectionInfoGeneric(ctx) if err != nil { return err } diff --git a/cli/ping.go b/cli/ping.go index fbf6c46fd6b24..17920c013e0a9 100644 --- a/cli/ping.go +++ b/cli/ping.go @@ -56,7 +56,7 @@ func (r *RootCmd) ping() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, diff --git a/cli/portforward.go b/cli/portforward.go index 70f462d08a529..95ca8731f99fd 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -103,7 +103,7 @@ func (r *RootCmd) portForward() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, diff --git a/cli/speedtest.go b/cli/speedtest.go index 7f208b9941fc8..dff9dabdf1bb6 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -60,7 +60,7 @@ func (r *RootCmd) speedtest() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, }) diff --git a/cli/ssh.go b/cli/ssh.go index 32b28a9c64ad1..fc788417e5c8e 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -223,7 +223,7 @@ func (r *RootCmd) ssh() *serpent.Command { if r.disableDirect { _, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.") } - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, diff --git a/cli/vscodessh.go b/cli/vscodessh.go index 2c22c6a622522..65f2bc7bd1274 100644 --- a/cli/vscodessh.go +++ b/cli/vscodessh.go @@ -165,7 +165,7 @@ func (r *RootCmd) vscodeSSH() *serpent.Command { if r.disableDirect { logger.Info(ctx, "direct connections disabled") } - agentConn, err := workspacesdk.NewClient(client). + agentConn, err := workspacesdk.New(client). DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{ Logger: logger, BlockEndpoints: r.disableDirect, diff --git a/coderd/activitybump_test.go b/coderd/activitybump_test.go index 93e6bd7fe3038..20c17b8d27762 100644 --- a/coderd/activitybump_test.go +++ b/coderd/activitybump_test.go @@ -166,7 +166,7 @@ func TestWorkspaceActivityBump(t *testing.T) { client, workspace, assertBumped := setupActivityTest(t) resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil), }) @@ -204,7 +204,7 @@ func TestWorkspaceActivityBump(t *testing.T) { // Bump by dialing the workspace and sending traffic. resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil), }) diff --git a/coderd/coderd_test.go b/coderd/coderd_test.go index 51613775bd736..eb03e7ebcf9fb 100644 --- a/coderd/coderd_test.go +++ b/coderd/coderd_test.go @@ -190,7 +190,7 @@ func TestDERPForceWebSockets(t *testing.T) { t.Cleanup(func() { client.HTTPClient.CloseIdleConnections() }) - wsclient := workspacesdk.NewClient(client) + wsclient := workspacesdk.New(client) user := coderdtest.CreateFirstUser(t, client) gen, err := wsclient.AgentConnectionInfoGeneric(context.Background()) diff --git a/coderd/debug_test.go b/coderd/debug_test.go index 07b2bacf8de99..0d5dfd1885f12 100644 --- a/coderd/debug_test.go +++ b/coderd/debug_test.go @@ -251,7 +251,7 @@ func TestHealthSettings(t *testing.T) { _ = coderdtest.CreateFirstUser(t, adminClient) // when - settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) + settings, err := healthsdk.New(adminClient).HealthSettings(ctx) require.NoError(t, err) // then @@ -273,11 +273,11 @@ func TestHealthSettings(t *testing.T) { } // when: dismiss "derp" and "websocket" - err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) + err := healthsdk.New(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // then - settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) + settings, err := healthsdk.New(adminClient).HealthSettings(ctx) require.NoError(t, err) require.Equal(t, expected, settings) @@ -307,7 +307,7 @@ func TestHealthSettings(t *testing.T) { DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, } - err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, initial) + err := healthsdk.New(adminClient).PutHealthSettings(ctx, initial) require.NoError(t, err) expected := healthsdk.HealthSettings{ @@ -315,11 +315,11 @@ func TestHealthSettings(t *testing.T) { } // when: undismiss "websocket" - err = healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) + err = healthsdk.New(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // then - settings, err := healthsdk.NewHealthClient(adminClient).HealthSettings(ctx) + settings, err := healthsdk.New(adminClient).HealthSettings(ctx) require.NoError(t, err) require.Equal(t, expected, settings) @@ -349,11 +349,11 @@ func TestHealthSettings(t *testing.T) { DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket}, } - err := healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) + err := healthsdk.New(adminClient).PutHealthSettings(ctx, expected) require.NoError(t, err) // when - err = healthsdk.NewHealthClient(adminClient).PutHealthSettings(ctx, expected) + err = healthsdk.New(adminClient).PutHealthSettings(ctx, expected) // then require.Error(t, err) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 9be9929c58d94..c62ef41b707d7 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -87,7 +87,7 @@ func TestDeploymentInsights(t *testing.T) { require.NoError(t, err) assert.NotZero(t, res.Workspaces[0].LastUsedAt) - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Named("tailnet"), }) @@ -176,7 +176,7 @@ func TestUserActivityInsights_SanityCheck(t *testing.T) { defer cancel() // Connect to the agent to generate usage/latency stats. - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: logger.Named("client"), }) @@ -274,7 +274,7 @@ func TestUserLatencyInsights(t *testing.T) { defer cancel() // Connect to the agent to generate usage/latency stats. - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: logger.Named("client"), }) diff --git a/coderd/templates_test.go b/coderd/templates_test.go index 82eecbf5b33d0..f0d6bf14335c9 100644 --- a/coderd/templates_test.go +++ b/coderd/templates_test.go @@ -1236,7 +1236,7 @@ func TestTemplateMetrics(t *testing.T) { require.NoError(t, err) assert.Zero(t, res.Workspaces[0].LastUsedAt) - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Named("tailnet"), }) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 7baf4be58974d..73f0c74cd765a 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -338,7 +338,7 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, nil) require.NoError(t, err) defer func() { @@ -454,7 +454,7 @@ func TestWorkspaceAgentTailnet(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() // Connection should remain open even if the dial context is canceled. - return workspacesdk.NewClient(client). + return workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), }) @@ -566,7 +566,7 @@ func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) { } } - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug), }) @@ -1631,7 +1631,7 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() // Connection should remain open even if the dial context is canceled. - return workspacesdk.NewClient(client). + return workspacesdk.New(client). DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ Logger: logger.Named("client1"), }) @@ -1677,7 +1677,7 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) { require.True(t, ok) // Connect from a second client. - conn2, err := workspacesdk.NewClient(client). + conn2, err := workspacesdk.New(client). DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ Logger: logger.Named("client2"), }) diff --git a/coderd/workspaceapps/apptest/apptest.go b/coderd/workspaceapps/apptest/apptest.go index 0503f5b087f2a..dea232c867cb9 100644 --- a/coderd/workspaceapps/apptest/apptest.go +++ b/coderd/workspaceapps/apptest/apptest.go @@ -1721,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 := workspacesdk.NewClient(client).AgentReconnectingPTY(ctx, opts) + conn, err := workspacesdk.New(client).AgentReconnectingPTY(ctx, opts) require.NoError(t, err) defer conn.Close() diff --git a/codersdk/healthsdk/health.go b/codersdk/healthsdk/healthsdk.go similarity index 99% rename from codersdk/healthsdk/health.go rename to codersdk/healthsdk/healthsdk.go index eb728b6531d46..26e72e4e5f5d8 100644 --- a/codersdk/healthsdk/health.go +++ b/codersdk/healthsdk/healthsdk.go @@ -20,7 +20,7 @@ type HealthClient struct { client *codersdk.Client } -func NewHealthClient(c *codersdk.Client) *HealthClient { +func New(c *codersdk.Client) *HealthClient { return &HealthClient{client: c} } diff --git a/codersdk/workspacesdk/workspaceagentconn.go b/codersdk/workspacesdk/agentconn.go similarity index 81% rename from codersdk/workspacesdk/workspaceagentconn.go rename to codersdk/workspacesdk/agentconn.go index 55de430efd0f5..6700f5d935273 100644 --- a/codersdk/workspacesdk/workspaceagentconn.go +++ b/codersdk/workspacesdk/agentconn.go @@ -9,9 +9,7 @@ import ( "net" "net/http" "net/netip" - "os" "strconv" - "strings" "time" "github.com/google/uuid" @@ -27,108 +25,6 @@ import ( "github.com/coder/coder/v2/tailnet" ) -// 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{}{} - } -} - // 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 diff --git a/codersdk/workspacesdk/workspaceagents.go b/codersdk/workspacesdk/connector.go similarity index 51% rename from codersdk/workspacesdk/workspaceagents.go rename to codersdk/workspacesdk/connector.go index c42dd3237c488..5c1d9e600aede 100644 --- a/codersdk/workspacesdk/workspaceagents.go +++ b/codersdk/workspacesdk/connector.go @@ -2,15 +2,9 @@ package workspacesdk import ( "context" - "encoding/json" "errors" - "fmt" "io" - "net" "net/http" - "net/http/cookiejar" - "net/netip" - "strconv" "sync" "time" @@ -26,158 +20,6 @@ import ( "github.com/coder/retry" ) -type Client struct { - client *codersdk.Client -} - -func NewClient(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 -} - // tailnetConn is the subset of the tailnet.Conn methods that tailnetAPIConnector uses. It is // included so that we can fake it in testing. // @@ -390,66 +232,3 @@ func (tac *tailnetAPIConnector) derpMap(client proto.DRPCTailnetClient) error { tac.conn.SetDERPMap(dm) } } - -// @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/workspacesdk/workspacesdk.go b/codersdk/workspacesdk/workspacesdk.go new file mode 100644 index 0000000000000..8af29f8f76f1f --- /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/workspacesdk/workspaceagents_internal_test.go b/codersdk/workspacesdk/workspacesdk_internal_test.go similarity index 100% rename from codersdk/workspacesdk/workspaceagents_internal_test.go rename to codersdk/workspacesdk/workspacesdk_internal_test.go diff --git a/codersdk/workspacesdk/workspaceagents_test.go b/codersdk/workspacesdk/workspacesdk_test.go similarity index 100% rename from codersdk/workspacesdk/workspaceagents_test.go rename to codersdk/workspacesdk/workspacesdk_test.go diff --git a/enterprise/coderd/coderd_test.go b/enterprise/coderd/coderd_test.go index 4857f1e1a2e23..1b156a4355c89 100644 --- a/enterprise/coderd/coderd_test.go +++ b/enterprise/coderd/coderd_test.go @@ -273,7 +273,7 @@ func TestAuditLogging(t *testing.T) { DontAddLicense: true, }) r := setupWorkspaceAgent(t, client, user, 0) - conn, err := workspacesdk.NewClient(client).DialAgent(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 bdeb9466253f7..595f2fe375836 100644 --- a/enterprise/coderd/replicas_test.go +++ b/enterprise/coderd/replicas_test.go @@ -83,7 +83,7 @@ func TestReplicas(t *testing.T) { require.Len(t, replicas, 2) r := setupWorkspaceAgent(t, firstClient, firstUser, 0) - conn, err := workspacesdk.NewClient(secondClient). + conn, err := workspacesdk.New(secondClient). DialAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialAgentOptions{ BlockEndpoints: true, Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), @@ -130,7 +130,7 @@ func TestReplicas(t *testing.T) { require.Len(t, replicas, 2) r := setupWorkspaceAgent(t, firstClient, firstUser, 0) - conn, err := workspacesdk.NewClient(secondClient). + 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), diff --git a/enterprise/coderd/workspaceagents_test.go b/enterprise/coderd/workspaceagents_test.go index da9f45517500b..17b38a0332570 100644 --- a/enterprise/coderd/workspaceagents_test.go +++ b/enterprise/coderd/workspaceagents_test.go @@ -47,7 +47,7 @@ func TestBlockNonBrowser(t *testing.T) { }) r := setupWorkspaceAgent(t, client, user, 0) //nolint:gocritic // Testing that even the owner gets blocked. - _, err := workspacesdk.NewClient(client).DialAgent(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()) @@ -66,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 := workspacesdk.NewClient(client).DialAgent(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/wsproxy/wsproxy_test.go b/enterprise/wsproxy/wsproxy_test.go index de05a0e445c4f..145a69a95e846 100644 --- a/enterprise/wsproxy/wsproxy_test.go +++ b/enterprise/wsproxy/wsproxy_test.go @@ -197,7 +197,7 @@ resourceLoop: t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) - connInfo, err := workspacesdk.NewClient(client).AgentConnectionInfo(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 @@ -270,7 +270,7 @@ resourceLoop: t.Parallel() ctx := testutil.Context(t, testutil.WaitLong) - connInfo, err := workspacesdk.NewClient(client).AgentConnectionInfo(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())) @@ -430,7 +430,7 @@ resourceLoop: _ = coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) // Connect to the workspace agent. - conn, err := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ Logger: slogtest.Make(t, &slogtest.Options{ IgnoreErrors: true, diff --git a/enterprise/wsproxy/wsproxysdk/wsproxysdk.go b/enterprise/wsproxy/wsproxysdk/wsproxysdk.go index 2ae2a6fc5906b..b3b833fb37c3a 100644 --- a/enterprise/wsproxy/wsproxysdk/wsproxysdk.go +++ b/enterprise/wsproxy/wsproxysdk/wsproxysdk.go @@ -78,7 +78,7 @@ 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 *workspacesdk.DialAgentOptions) (agentConn *workspacesdk.AgentConn, err error) { - return workspacesdk.NewClient(c.SDKClient).DialAgent(ctx, agentID, options) + 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 c85a0a3ddb243..a5aaddee4e1d1 100644 --- a/scaletest/agentconn/run.go +++ b/scaletest/agentconn/run.go @@ -63,7 +63,7 @@ func (r *Runner) Run(ctx context.Context, _ string, w io.Writer) error { _, _ = fmt.Fprintln(logs, "\tUsing proxied DERP connection through coder server...") } - conn, err := workspacesdk.NewClient(r.client). + 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. diff --git a/scaletest/reconnectingpty/run.go b/scaletest/reconnectingpty/run.go index 2ac62f6bdeab8..ce66c42f1658d 100644 --- a/scaletest/reconnectingpty/run.go +++ b/scaletest/reconnectingpty/run.go @@ -65,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 := workspacesdk.NewClient(r.client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ + conn, err := workspacesdk.New(r.client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ AgentID: r.cfg.AgentID, Reconnect: id, Width: width, diff --git a/scaletest/workspacetraffic/conn.go b/scaletest/workspacetraffic/conn.go index beb6a94d0c71b..455feaa8f6d43 100644 --- a/scaletest/workspacetraffic/conn.go +++ b/scaletest/workspacetraffic/conn.go @@ -39,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 := workspacesdk.NewClient(client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ + conn, err := workspacesdk.New(client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{ AgentID: agentID, Reconnect: reconnect, Width: uint16(width), @@ -157,7 +157,7 @@ func connectSSH(ctx context.Context, client *codersdk.Client, agentID uuid.UUID, } }() - agentConn, err := workspacesdk.NewClient(client).DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{}) + 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/support/support.go b/support/support.go index 3ec58cdd1982f..47cad76a7d665 100644 --- a/support/support.go +++ b/support/support.go @@ -112,7 +112,7 @@ func DeploymentInfo(ctx context.Context, client *codersdk.Client, log slog.Logge }) eg.Go(func() error { - hr, err := healthsdk.NewHealthClient(client).DebugHealth(ctx) + hr, err := healthsdk.New(client).DebugHealth(ctx) if err != nil { return xerrors.Errorf("fetch health report: %w", err) } @@ -175,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 := workspacesdk.NewClient(client).AgentConnectionInfo(ctx, agentID) + connInfo, err := workspacesdk.New(client).AgentConnectionInfo(ctx, agentID) if err != nil { return xerrors.Errorf("fetch agent conn info: %w", err) } @@ -333,7 +333,7 @@ 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 := workspacesdk.NewClient(client). + conn, err := workspacesdk.New(client). DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{ Logger: log.Named("dial-agent"), BlockEndpoints: false, From 1cd79f6772e9f1b30cb01ae7ec947f8c5d955e1a Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Tue, 26 Mar 2024 17:27:43 +0000 Subject: [PATCH 11/11] fixup! rearrange --- site/src/api/typesGenerated.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 703a7bac95827..3f78a3b81ecbc 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -2278,7 +2278,7 @@ export type RegionTypes = Region | WorkspaceProxy; // The code below is generated from codersdk/healthsdk. -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface AccessURLReport { readonly healthy: boolean; readonly severity: HealthSeverity; @@ -2291,7 +2291,7 @@ export interface AccessURLReport { readonly error?: string; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface DERPHealthReport { readonly healthy: boolean; readonly severity: HealthSeverity; @@ -2306,7 +2306,7 @@ export interface DERPHealthReport { readonly error?: string; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface DERPNodeReport { readonly healthy: boolean; readonly severity: HealthSeverity; @@ -2327,7 +2327,7 @@ export interface DERPNodeReport { readonly stun: STUNReport; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface DERPRegionReport { readonly healthy: boolean; readonly severity: HealthSeverity; @@ -2339,7 +2339,7 @@ export interface DERPRegionReport { readonly error?: string; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface DatabaseReport { readonly healthy: boolean; readonly severity: HealthSeverity; @@ -2352,12 +2352,12 @@ export interface DatabaseReport { readonly error?: string; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface HealthSettings { readonly dismissed_healthchecks: HealthSection[]; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface HealthcheckReport { readonly time: string; readonly healthy: boolean; @@ -2372,7 +2372,7 @@ export interface HealthcheckReport { readonly coder_version: string; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface ProvisionerDaemonsReport { readonly severity: HealthSeverity; readonly warnings: HealthMessage[]; @@ -2381,25 +2381,25 @@ export interface ProvisionerDaemonsReport { readonly items: ProvisionerDaemonsReportItem[]; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface ProvisionerDaemonsReportItem { readonly provisioner_daemon: ProvisionerDaemon; readonly warnings: HealthMessage[]; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface STUNReport { readonly Enabled: boolean; readonly CanSTUN: boolean; readonly Error?: string; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface UpdateHealthSettings { readonly dismissed_healthchecks: HealthSection[]; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface WebsocketReport { readonly healthy: boolean; readonly severity: HealthSeverity; @@ -2410,7 +2410,7 @@ export interface WebsocketReport { readonly error?: string; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export interface WorkspaceProxyReport { readonly healthy: boolean; readonly severity: HealthSeverity; @@ -2420,7 +2420,7 @@ export interface WorkspaceProxyReport { readonly workspace_proxies: RegionsResponse; } -// From healthsdk/health.go +// From healthsdk/healthsdk.go export type HealthSection = | "AccessURL" | "DERP"