mirror of https://github.com/coder/coder.git
chore(codersdk): move all tailscale imports out of `codersdk` (#12735)
Currently, importing `codersdk` just to interact with the API requires importing tailscale, which causes builds to fail unless manually using our fork.
This commit is contained in:
parent
0bea8906d4
commit
4d5a7b2d56
|
@ -38,8 +38,6 @@ import (
|
||||||
"tailscale.com/util/clientmetric"
|
"tailscale.com/util/clientmetric"
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"github.com/coder/retry"
|
|
||||||
|
|
||||||
"github.com/coder/coder/v2/agent/agentproc"
|
"github.com/coder/coder/v2/agent/agentproc"
|
||||||
"github.com/coder/coder/v2/agent/agentscripts"
|
"github.com/coder/coder/v2/agent/agentscripts"
|
||||||
"github.com/coder/coder/v2/agent/agentssh"
|
"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/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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"
|
||||||
tailnetproto "github.com/coder/coder/v2/tailnet/proto"
|
tailnetproto "github.com/coder/coder/v2/tailnet/proto"
|
||||||
|
"github.com/coder/retry"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1107,7 +1107,7 @@ func (a *agent) wireguardAddresses(agentID uuid.UUID) []netip.Prefix {
|
||||||
netip.PrefixFrom(tailnet.IPFromUUID(agentID), 128),
|
netip.PrefixFrom(tailnet.IPFromUUID(agentID), 128),
|
||||||
// We also listen on the legacy codersdk.WorkspaceAgentIP. This
|
// We also listen on the legacy codersdk.WorkspaceAgentIP. This
|
||||||
// allows for a transition away from wsconncache.
|
// allows for a transition away from wsconncache.
|
||||||
netip.PrefixFrom(codersdk.WorkspaceAgentIP, 128),
|
netip.PrefixFrom(workspacesdk.AgentIP, 128),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1147,7 +1147,7 @@ func (a *agent) createTailnet(ctx context.Context, agentID uuid.UUID, derpMap *t
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.WorkspaceAgentSSHPort))
|
sshListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentSSHPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("listen on the ssh port: %w", err)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reconnectingPTYListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.WorkspaceAgentReconnectingPTYPort))
|
reconnectingPTYListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentReconnectingPTYPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("listen for reconnecting pty: %w", err)
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var msg codersdk.WorkspaceAgentReconnectingPTYInit
|
var msg workspacesdk.AgentReconnectingPTYInit
|
||||||
err = json.Unmarshal(data, &msg)
|
err = json.Unmarshal(data, &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(ctx, "failed to unmarshal init", slog.F("raw", data))
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
speedtestListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.WorkspaceAgentSpeedtestPort))
|
speedtestListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentSpeedtestPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("listen for speedtest: %w", err)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
apiListener, err := network.Listen("tcp", ":"+strconv.Itoa(codersdk.WorkspaceAgentHTTPAPIServerPort))
|
apiListener, err := network.Listen("tcp", ":"+strconv.Itoa(workspacesdk.AgentHTTPAPIServerPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("api listener: %w", err)
|
return nil, xerrors.Errorf("api listener: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -1386,7 +1386,7 @@ func (a *agent) runDERPMapSubscriber(ctx context.Context, conn drpc.Conn, networ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, msg codersdk.WorkspaceAgentReconnectingPTYInit, conn net.Conn) (retErr error) {
|
func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, msg workspacesdk.AgentReconnectingPTYInit, conn net.Conn) (retErr error) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
a.metrics.connectionsTotal.Add(1)
|
a.metrics.connectionsTotal.Add(1)
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@ import (
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"cdr.dev/slog/sloggers/sloghuman"
|
"cdr.dev/slog/sloggers/sloghuman"
|
||||||
"cdr.dev/slog/sloggers/slogtest"
|
"cdr.dev/slog/sloggers/slogtest"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/agent"
|
"github.com/coder/coder/v2/agent"
|
||||||
"github.com/coder/coder/v2/agent/agentproc"
|
"github.com/coder/coder/v2/agent/agentproc"
|
||||||
"github.com/coder/coder/v2/agent/agentproc/agentproctest"
|
"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/agent/proto"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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/cryptorand"
|
||||||
"github.com/coder/coder/v2/pty/ptytest"
|
"github.com/coder/coder/v2/pty/ptytest"
|
||||||
"github.com/coder/coder/v2/tailnet"
|
"github.com/coder/coder/v2/tailnet"
|
||||||
|
@ -113,7 +113,7 @@ func TestAgent_Stats_ReconnectingPTY(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer ptyConn.Close()
|
defer ptyConn.Close()
|
||||||
|
|
||||||
data, err := json.Marshal(codersdk.ReconnectingPTYRequest{
|
data, err := json.Marshal(workspacesdk.ReconnectingPTYRequest{
|
||||||
Data: "echo test\r\n",
|
Data: "echo test\r\n",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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, tr1.ReadUntil(ctx, matchPrompt), "find prompt")
|
||||||
require.NoError(t, tr2.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",
|
Data: "echo test\r",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -1634,7 +1634,7 @@ func TestAgent_ReconnectingPTY(t *testing.T) {
|
||||||
require.NoError(t, tr3.ReadUntil(ctx, matchEchoOutput), "find echo output")
|
require.NoError(t, tr3.ReadUntil(ctx, matchEchoOutput), "find echo output")
|
||||||
|
|
||||||
// Exit should cause the connection to close.
|
// Exit should cause the connection to close.
|
||||||
data, err = json.Marshal(codersdk.ReconnectingPTYRequest{
|
data, err = json.Marshal(workspacesdk.ReconnectingPTYRequest{
|
||||||
Data: "exit\r",
|
Data: "exit\r",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -1783,7 +1783,7 @@ func TestAgent_UpdatedDERP(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Setup a client connection.
|
// Setup a client connection.
|
||||||
newClientConn := func(derpMap *tailcfg.DERPMap, name string) *codersdk.WorkspaceAgentConn {
|
newClientConn := func(derpMap *tailcfg.DERPMap, name string) *workspacesdk.AgentConn {
|
||||||
conn, err := tailnet.NewConn(&tailnet.Options{
|
conn, err := tailnet.NewConn(&tailnet.Options{
|
||||||
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
|
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
|
||||||
DERPMap: derpMap,
|
DERPMap: derpMap,
|
||||||
|
@ -1812,9 +1812,9 @@ func TestAgent_UpdatedDERP(t *testing.T) {
|
||||||
// Force DERP.
|
// Force DERP.
|
||||||
conn.SetBlockEndpoints(true)
|
conn.SetBlockEndpoints(true)
|
||||||
|
|
||||||
sdkConn := codersdk.NewWorkspaceAgentConn(conn, codersdk.WorkspaceAgentConnOptions{
|
sdkConn := workspacesdk.NewAgentConn(conn, workspacesdk.AgentConnOptions{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
CloseFunc: func() error { return codersdk.ErrSkipClose },
|
CloseFunc: func() error { return workspacesdk.ErrSkipClose },
|
||||||
})
|
})
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
t.Logf("closing sdkConn %s", name)
|
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)) (
|
func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Duration, opts ...func(*agenttest.Client, *agent.Options)) (
|
||||||
*codersdk.WorkspaceAgentConn,
|
*workspacesdk.AgentConn,
|
||||||
*agenttest.Client,
|
*agenttest.Client,
|
||||||
<-chan *proto.Stats,
|
<-chan *proto.Stats,
|
||||||
afero.Fs,
|
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())
|
t.Logf("error closing in-mem coordination: %s", err.Error())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
agentConn := codersdk.NewWorkspaceAgentConn(conn, codersdk.WorkspaceAgentConnOptions{
|
agentConn := workspacesdk.NewAgentConn(conn, workspacesdk.AgentConnOptions{
|
||||||
AgentID: metadata.AgentID,
|
AgentID: metadata.AgentID,
|
||||||
})
|
})
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) {
|
func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentListeningPort, error) {
|
||||||
|
@ -32,7 +33,7 @@ func (lp *listeningPortsHandler) getListeningPorts() ([]codersdk.WorkspaceAgentL
|
||||||
seen := make(map[uint16]struct{}, len(tabs))
|
seen := make(map[uint16]struct{}, len(tabs))
|
||||||
ports := []codersdk.WorkspaceAgentListeningPort{}
|
ports := []codersdk.WorkspaceAgentListeningPort{}
|
||||||
for _, tab := range tabs {
|
for _, tab := range tabs {
|
||||||
if tab.LocalAddr == nil || tab.LocalAddr.Port < codersdk.WorkspaceAgentMinimumListeningPort {
|
if tab.LocalAddr == nil || tab.LocalAddr.Port < workspacesdk.AgentMinimumListeningPort {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,7 @@ import (
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
|
||||||
"github.com/coder/coder/v2/pty"
|
"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) {
|
func readConnLoop(ctx context.Context, conn net.Conn, ptty pty.PTYCmd, metrics *prometheus.CounterVec, logger slog.Logger) {
|
||||||
decoder := json.NewDecoder(conn)
|
decoder := json.NewDecoder(conn)
|
||||||
for {
|
for {
|
||||||
var req codersdk.ReconnectingPTYRequest
|
var req workspacesdk.ReconnectingPTYRequest
|
||||||
err := decoder.Decode(&req)
|
err := decoder.Decode(&req)
|
||||||
if xerrors.Is(err, io.EOF) {
|
if xerrors.Is(err, io.EOF) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbfake"
|
"github.com/coder/coder/v2/coderd/database/dbfake"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/provisionersdk/proto"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"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) {
|
if assert.NotEmpty(t, workspace.LatestBuild.Resources) && assert.NotEmpty(t, resources[0].Agents) {
|
||||||
assert.NotEmpty(t, resources[0].Agents[0].Version)
|
assert.NotEmpty(t, resources[0].Agents[0].Version)
|
||||||
}
|
}
|
||||||
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
|
dialer, err := workspacesdk.New(client).
|
||||||
|
DialAgent(ctx, resources[0].Agents[0].ID, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer dialer.Close()
|
defer dialer.Close()
|
||||||
require.True(t, dialer.AwaitReachable(ctx))
|
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) {
|
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
|
||||||
assert.NotEmpty(t, resources[0].Agents[0].Version)
|
assert.NotEmpty(t, resources[0].Agents[0].Version)
|
||||||
}
|
}
|
||||||
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
|
dialer, err := workspacesdk.New(client).
|
||||||
|
DialAgent(ctx, resources[0].Agents[0].ID, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer dialer.Close()
|
defer dialer.Close()
|
||||||
require.True(t, dialer.AwaitReachable(ctx))
|
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) {
|
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
|
||||||
assert.NotEmpty(t, resources[0].Agents[0].Version)
|
assert.NotEmpty(t, resources[0].Agents[0].Version)
|
||||||
}
|
}
|
||||||
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
|
dialer, err := workspacesdk.New(client).DialAgent(ctx, resources[0].Agents[0].ID, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer dialer.Close()
|
defer dialer.Close()
|
||||||
require.True(t, dialer.AwaitReachable(ctx))
|
require.True(t, dialer.AwaitReachable(ctx))
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbfake"
|
"github.com/coder/coder/v2/coderd/database/dbfake"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/provisionersdk/proto"
|
||||||
"github.com/coder/coder/v2/pty/ptytest"
|
"github.com/coder/coder/v2/pty/ptytest"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
|
@ -83,7 +84,8 @@ func TestConfigSSH(t *testing.T) {
|
||||||
}).WithAgent().Do()
|
}).WithAgent().Do()
|
||||||
_ = agenttest.New(t, client.URL, r.AgentToken)
|
_ = agenttest.New(t, client.URL, r.AgentToken)
|
||||||
resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
|
||||||
agentConn, err := client.DialWorkspaceAgent(context.Background(), resources[0].Agents[0].ID, nil)
|
agentConn, err := workspacesdk.New(client).
|
||||||
|
DialAgent(context.Background(), resources[0].Agents[0].ID, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer agentConn.Close()
|
defer agentConn.Close()
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/coderd/tracing"
|
"github.com/coder/coder/v2/coderd/tracing"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/coder/v2/cryptorand"
|
"github.com/coder/coder/v2/cryptorand"
|
||||||
"github.com/coder/coder/v2/scaletest/agentconn"
|
"github.com/coder/coder/v2/scaletest/agentconn"
|
||||||
"github.com/coder/coder/v2/scaletest/createworkspaces"
|
"github.com/coder/coder/v2/scaletest/createworkspaces"
|
||||||
|
@ -667,7 +668,7 @@ func (r *RootCmd) scaletestCreateWorkspaces() *serpent.Command {
|
||||||
if runCommand != "" {
|
if runCommand != "" {
|
||||||
config.ReconnectingPTY = &reconnectingpty.Config{
|
config.ReconnectingPTY = &reconnectingpty.Config{
|
||||||
// AgentID is set by the test automatically.
|
// AgentID is set by the test automatically.
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
Height: 24,
|
Height: 24,
|
||||||
Width: 80,
|
Width: 80,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/serpent"
|
"github.com/coder/serpent"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ func (r *RootCmd) netcheck() *serpent.Command {
|
||||||
ctx, cancel := context.WithTimeout(inv.Context(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(inv.Context(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
connInfo, err := client.WorkspaceAgentConnectionInfoGeneric(ctx)
|
connInfo, err := workspacesdk.New(client).AgentConnectionInfoGeneric(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/cli/clitest"
|
"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"
|
"github.com/coder/coder/v2/pty/ptytest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func TestNetcheck(t *testing.T) {
|
||||||
|
|
||||||
b := out.Bytes()
|
b := out.Bytes()
|
||||||
t.Log(string(b))
|
t.Log(string(b))
|
||||||
var report codersdk.DERPHealthReport
|
var report healthsdk.DERPHealthReport
|
||||||
require.NoError(t, json.Unmarshal(b, &report))
|
require.NoError(t, json.Unmarshal(b, &report))
|
||||||
|
|
||||||
assert.True(t, report.Healthy)
|
assert.True(t, report.Healthy)
|
||||||
|
|
10
cli/ping.go
10
cli/ping.go
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/coder/coder/v2/cli/cliui"
|
"github.com/coder/coder/v2/cli/cliui"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/serpent"
|
"github.com/coder/serpent"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,10 +56,11 @@ func (r *RootCmd) ping() *serpent.Command {
|
||||||
if r.disableDirect {
|
if r.disableDirect {
|
||||||
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
|
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
|
||||||
}
|
}
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: logger,
|
DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{
|
||||||
BlockEndpoints: r.disableDirect,
|
Logger: logger,
|
||||||
})
|
BlockEndpoints: r.disableDirect,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/coder/coder/v2/agent/agentssh"
|
"github.com/coder/coder/v2/agent/agentssh"
|
||||||
"github.com/coder/coder/v2/cli/cliui"
|
"github.com/coder/coder/v2/cli/cliui"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/serpent"
|
"github.com/coder/serpent"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -102,10 +103,11 @@ func (r *RootCmd) portForward() *serpent.Command {
|
||||||
if r.disableDirect {
|
if r.disableDirect {
|
||||||
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
|
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
|
||||||
}
|
}
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: logger,
|
DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{
|
||||||
BlockEndpoints: r.disableDirect,
|
Logger: logger,
|
||||||
})
|
BlockEndpoints: r.disableDirect,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -193,7 +195,7 @@ func (r *RootCmd) portForward() *serpent.Command {
|
||||||
func listenAndPortForward(
|
func listenAndPortForward(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
inv *serpent.Invocation,
|
inv *serpent.Invocation,
|
||||||
conn *codersdk.WorkspaceAgentConn,
|
conn *workspacesdk.AgentConn,
|
||||||
wg *sync.WaitGroup,
|
wg *sync.WaitGroup,
|
||||||
spec portForwardSpec,
|
spec portForwardSpec,
|
||||||
logger slog.Logger,
|
logger slog.Logger,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"cdr.dev/slog/sloggers/sloghuman"
|
"cdr.dev/slog/sloggers/sloghuman"
|
||||||
"github.com/coder/coder/v2/cli/cliui"
|
"github.com/coder/coder/v2/cli/cliui"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/serpent"
|
"github.com/coder/serpent"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,9 +60,10 @@ func (r *RootCmd) speedtest() *serpent.Command {
|
||||||
if r.disableDirect {
|
if r.disableDirect {
|
||||||
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
|
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
|
||||||
}
|
}
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: logger,
|
DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: logger,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
10
cli/ssh.go
10
cli/ssh.go
|
@ -36,6 +36,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/autobuild/notify"
|
"github.com/coder/coder/v2/coderd/autobuild/notify"
|
||||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/coder/v2/cryptorand"
|
"github.com/coder/coder/v2/cryptorand"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -222,10 +223,11 @@ func (r *RootCmd) ssh() *serpent.Command {
|
||||||
if r.disableDirect {
|
if r.disableDirect {
|
||||||
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
|
_, _ = fmt.Fprintln(inv.Stderr, "Direct connections disabled.")
|
||||||
}
|
}
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: logger,
|
DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{
|
||||||
BlockEndpoints: r.disableDirect,
|
Logger: logger,
|
||||||
})
|
BlockEndpoints: r.disableDirect,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("dial agent: %w", err)
|
return xerrors.Errorf("dial agent: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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/provisionersdk/proto"
|
||||||
"github.com/coder/coder/v2/tailnet"
|
"github.com/coder/coder/v2/tailnet"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
|
@ -159,7 +161,7 @@ func assertBundleContents(t *testing.T, path string, badValues ...string) {
|
||||||
decodeJSONFromZip(t, f, &v)
|
decodeJSONFromZip(t, f, &v)
|
||||||
require.NotEmpty(t, f, v, "experiments should not be empty")
|
require.NotEmpty(t, f, v, "experiments should not be empty")
|
||||||
case "deployment/health.json":
|
case "deployment/health.json":
|
||||||
var v codersdk.HealthcheckReport
|
var v healthsdk.HealthcheckReport
|
||||||
decodeJSONFromZip(t, f, &v)
|
decodeJSONFromZip(t, f, &v)
|
||||||
require.NotEmpty(t, v, "health report should not be empty")
|
require.NotEmpty(t, v, "health report should not be empty")
|
||||||
case "network/coordinator_debug.html":
|
case "network/coordinator_debug.html":
|
||||||
|
@ -169,7 +171,7 @@ func assertBundleContents(t *testing.T, path string, badValues ...string) {
|
||||||
bs := readBytesFromZip(t, f)
|
bs := readBytesFromZip(t, f)
|
||||||
require.NotEmpty(t, bs, "tailnet debug should not be empty")
|
require.NotEmpty(t, bs, "tailnet debug should not be empty")
|
||||||
case "network/netcheck.json":
|
case "network/netcheck.json":
|
||||||
var v codersdk.WorkspaceAgentConnectionInfo
|
var v workspacesdk.AgentConnectionInfo
|
||||||
decodeJSONFromZip(t, f, &v)
|
decodeJSONFromZip(t, f, &v)
|
||||||
require.NotEmpty(t, v, "connection info should not be empty")
|
require.NotEmpty(t, v, "connection info should not be empty")
|
||||||
case "workspace/workspace.json":
|
case "workspace/workspace.json":
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/coder/coder/v2/cli/cliui"
|
"github.com/coder/coder/v2/cli/cliui"
|
||||||
"github.com/coder/coder/v2/cli/cliutil"
|
"github.com/coder/coder/v2/cli/cliutil"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/serpent"
|
"github.com/coder/serpent"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -164,10 +165,11 @@ func (r *RootCmd) vscodeSSH() *serpent.Command {
|
||||||
if r.disableDirect {
|
if r.disableDirect {
|
||||||
logger.Info(ctx, "direct connections disabled")
|
logger.Info(ctx, "direct connections disabled")
|
||||||
}
|
}
|
||||||
agentConn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, &codersdk.DialWorkspaceAgentOptions{
|
agentConn, err := workspacesdk.New(client).
|
||||||
Logger: logger,
|
DialAgent(ctx, workspaceAgent.ID, &workspacesdk.DialAgentOptions{
|
||||||
BlockEndpoints: r.disableDirect,
|
Logger: logger,
|
||||||
})
|
BlockEndpoints: r.disableDirect,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("dial workspace agent: %w", err)
|
return xerrors.Errorf("dial workspace agent: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -280,7 +282,7 @@ type sshNetworkStats struct {
|
||||||
DownloadBytesSec int64 `json:"download_bytes_sec"`
|
DownloadBytesSec int64 `json:"download_bytes_sec"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectNetworkStats(ctx context.Context, agentConn *codersdk.WorkspaceAgentConn, start, end time.Time, counts map[netlogtype.Connection]netlogtype.Counts) (*sshNetworkStats, error) {
|
func collectNetworkStats(ctx context.Context, agentConn *workspacesdk.AgentConn, start, end time.Time, counts map[netlogtype.Connection]netlogtype.Counts) (*sshNetworkStats, error) {
|
||||||
latency, p2p, pingResult, err := agentConn.Ping(ctx)
|
latency, p2p, pingResult, err := agentConn.Ping(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/schedule"
|
"github.com/coder/coder/v2/coderd/schedule"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/provisioner/echo"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
)
|
)
|
||||||
|
@ -165,9 +166,10 @@ func TestWorkspaceActivityBump(t *testing.T) {
|
||||||
client, workspace, assertBumped := setupActivityTest(t)
|
client, workspace, assertBumped := setupActivityTest(t)
|
||||||
|
|
||||||
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: slogtest.Make(t, nil),
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: slogtest.Make(t, nil),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
@ -202,9 +204,10 @@ func TestWorkspaceActivityBump(t *testing.T) {
|
||||||
|
|
||||||
// Bump by dialing the workspace and sending traffic.
|
// Bump by dialing the workspace and sending traffic.
|
||||||
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: slogtest.Make(t, nil),
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: slogtest.Make(t, nil),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -37,7 +37,6 @@ import (
|
||||||
"tailscale.com/util/singleflight"
|
"tailscale.com/util/singleflight"
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
|
|
||||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||||
"github.com/coder/coder/v2/buildinfo"
|
"github.com/coder/coder/v2/buildinfo"
|
||||||
_ "github.com/coder/coder/v2/coderd/apidoc" // Used for swagger docs.
|
_ "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/coderd/workspaceusage"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/drpc"
|
"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/provisionerd/proto"
|
||||||
"github.com/coder/coder/v2/provisionersdk"
|
"github.com/coder/coder/v2/provisionersdk"
|
||||||
"github.com/coder/coder/v2/site"
|
"github.com/coder/coder/v2/site"
|
||||||
|
@ -153,7 +153,7 @@ type Options struct {
|
||||||
// workspace applications. It consists of both a signing and encryption key.
|
// workspace applications. It consists of both a signing and encryption key.
|
||||||
AppSecurityKey workspaceapps.SecurityKey
|
AppSecurityKey workspaceapps.SecurityKey
|
||||||
|
|
||||||
HealthcheckFunc func(ctx context.Context, apiKey string) *codersdk.HealthcheckReport
|
HealthcheckFunc func(ctx context.Context, apiKey string) *healthsdk.HealthcheckReport
|
||||||
HealthcheckTimeout time.Duration
|
HealthcheckTimeout time.Duration
|
||||||
HealthcheckRefresh time.Duration
|
HealthcheckRefresh time.Duration
|
||||||
WorkspaceProxiesFetchUpdater *atomic.Pointer[healthcheck.WorkspaceProxiesFetchUpdater]
|
WorkspaceProxiesFetchUpdater *atomic.Pointer[healthcheck.WorkspaceProxiesFetchUpdater]
|
||||||
|
@ -423,7 +423,7 @@ func New(options *Options) *API {
|
||||||
UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore,
|
UserQuietHoursScheduleStore: options.UserQuietHoursScheduleStore,
|
||||||
AccessControlStore: options.AccessControlStore,
|
AccessControlStore: options.AccessControlStore,
|
||||||
Experiments: experiments,
|
Experiments: experiments,
|
||||||
healthCheckGroup: &singleflight.Group[string, *codersdk.HealthcheckReport]{},
|
healthCheckGroup: &singleflight.Group[string, *healthsdk.HealthcheckReport]{},
|
||||||
Acquirer: provisionerdserver.NewAcquirer(
|
Acquirer: provisionerdserver.NewAcquirer(
|
||||||
ctx,
|
ctx,
|
||||||
options.Logger.Named("acquirer"),
|
options.Logger.Named("acquirer"),
|
||||||
|
@ -462,7 +462,7 @@ func New(options *Options) *API {
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.HealthcheckFunc == nil {
|
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.
|
// NOTE: dismissed healthchecks are marked in formatHealthcheck.
|
||||||
// Not here, as this result gets cached.
|
// Not here, as this result gets cached.
|
||||||
return healthcheck.Run(ctx, &healthcheck.ReportOptions{
|
return healthcheck.Run(ctx, &healthcheck.ReportOptions{
|
||||||
|
@ -1266,8 +1266,8 @@ type API struct {
|
||||||
// This is used to gate features that are not yet ready for production.
|
// This is used to gate features that are not yet ready for production.
|
||||||
Experiments codersdk.Experiments
|
Experiments codersdk.Experiments
|
||||||
|
|
||||||
healthCheckGroup *singleflight.Group[string, *codersdk.HealthcheckReport]
|
healthCheckGroup *singleflight.Group[string, *healthsdk.HealthcheckReport]
|
||||||
healthCheckCache atomic.Pointer[codersdk.HealthcheckReport]
|
healthCheckCache atomic.Pointer[healthsdk.HealthcheckReport]
|
||||||
|
|
||||||
statsBatcher *batchstats.Batcher
|
statsBatcher *batchstats.Batcher
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"cdr.dev/slog/sloggers/slogtest"
|
"cdr.dev/slog/sloggers/slogtest"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbfake"
|
"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/provisionersdk/proto"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/agent/agenttest"
|
"github.com/coder/coder/v2/agent/agenttest"
|
||||||
|
@ -189,9 +190,10 @@ func TestDERPForceWebSockets(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
client.HTTPClient.CloseIdleConnections()
|
client.HTTPClient.CloseIdleConnections()
|
||||||
})
|
})
|
||||||
|
wsclient := workspacesdk.New(client)
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
gen, err := client.WorkspaceAgentConnectionInfoGeneric(context.Background())
|
gen, err := wsclient.AgentConnectionInfoGeneric(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Log(spew.Sdump(gen))
|
t.Log(spew.Sdump(gen))
|
||||||
|
|
||||||
|
@ -213,8 +215,8 @@ func TestDERPForceWebSockets(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID,
|
conn, err := wsclient.DialAgent(ctx, resources[0].Agents[0].ID,
|
||||||
&codersdk.DialWorkspaceAgentOptions{
|
&workspacesdk.DialAgentOptions{
|
||||||
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug).Named("client"),
|
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug).Named("client"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -75,6 +75,7 @@ import (
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||||
"github.com/coder/coder/v2/codersdk/drpc"
|
"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/cryptorand"
|
||||||
"github.com/coder/coder/v2/provisioner/echo"
|
"github.com/coder/coder/v2/provisioner/echo"
|
||||||
"github.com/coder/coder/v2/provisionerd"
|
"github.com/coder/coder/v2/provisionerd"
|
||||||
|
@ -113,7 +114,7 @@ type Options struct {
|
||||||
TemplateScheduleStore schedule.TemplateScheduleStore
|
TemplateScheduleStore schedule.TemplateScheduleStore
|
||||||
Coordinator tailnet.Coordinator
|
Coordinator tailnet.Coordinator
|
||||||
|
|
||||||
HealthcheckFunc func(ctx context.Context, apiKey string) *codersdk.HealthcheckReport
|
HealthcheckFunc func(ctx context.Context, apiKey string) *healthsdk.HealthcheckReport
|
||||||
HealthcheckTimeout time.Duration
|
HealthcheckTimeout time.Duration
|
||||||
HealthcheckRefresh time.Duration
|
HealthcheckRefresh time.Duration
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"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
|
// AuditOAuthConvertState is never stored in the database. It is stored in a cookie
|
||||||
|
@ -25,8 +25,8 @@ type AuditOAuthConvertState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type HealthSettings struct {
|
type HealthSettings struct {
|
||||||
ID uuid.UUID `db:"id" json:"id"`
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
DismissedHealthchecks []codersdk.HealthSection `db:"dismissed_healthchecks" json:"dismissed_healthchecks"`
|
DismissedHealthchecks []healthsdk.HealthSection `db:"dismissed_healthchecks" json:"dismissed_healthchecks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Actions []rbac.Action
|
type Actions []rbac.Action
|
||||||
|
|
|
@ -14,13 +14,13 @@ import (
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/audit"
|
"github.com/coder/coder/v2/coderd/audit"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/coderd/httpmw"
|
"github.com/coder/coder/v2/coderd/httpmw"
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/healthsdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @Summary Debug Info Wireguard Coordinator
|
// @Summary Debug Info Wireguard Coordinator
|
||||||
|
@ -50,7 +50,7 @@ func (api *API) debugTailnet(rw http.ResponseWriter, r *http.Request) {
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Tags Debug
|
// @Tags Debug
|
||||||
// @Success 200 {object} codersdk.HealthcheckReport
|
// @Success 200 {object} healthsdk.HealthcheckReport
|
||||||
// @Router /debug/health [get]
|
// @Router /debug/health [get]
|
||||||
// @Param force query boolean false "Force a healthcheck to run"
|
// @Param force query boolean false "Force a healthcheck to run"
|
||||||
func (api *API) debugDeploymentHealth(rw http.ResponseWriter, r *http.Request) {
|
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.
|
// Create a new context not tied to the request.
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), api.Options.HealthcheckTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), api.Options.HealthcheckTimeout)
|
||||||
defer cancel()
|
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.
|
// Mark any sections previously marked as dismissed.
|
||||||
for _, d := range dismissed {
|
for _, d := range dismissed {
|
||||||
switch d {
|
switch d {
|
||||||
case codersdk.HealthSectionAccessURL:
|
case healthsdk.HealthSectionAccessURL:
|
||||||
hc.AccessURL.Dismissed = true
|
hc.AccessURL.Dismissed = true
|
||||||
case codersdk.HealthSectionDERP:
|
case healthsdk.HealthSectionDERP:
|
||||||
hc.DERP.Dismissed = true
|
hc.DERP.Dismissed = true
|
||||||
case codersdk.HealthSectionDatabase:
|
case healthsdk.HealthSectionDatabase:
|
||||||
hc.Database.Dismissed = true
|
hc.Database.Dismissed = true
|
||||||
case codersdk.HealthSectionWebsocket:
|
case healthsdk.HealthSectionWebsocket:
|
||||||
hc.Websocket.Dismissed = true
|
hc.Websocket.Dismissed = true
|
||||||
case codersdk.HealthSectionWorkspaceProxy:
|
case healthsdk.HealthSectionWorkspaceProxy:
|
||||||
hc.WorkspaceProxy.Dismissed = true
|
hc.WorkspaceProxy.Dismissed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ func formatHealthcheck(ctx context.Context, rw http.ResponseWriter, r *http.Requ
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Tags Debug
|
// @Tags Debug
|
||||||
// @Success 200 {object} codersdk.HealthSettings
|
// @Success 200 {object} healthsdk.HealthSettings
|
||||||
// @Router /debug/health/settings [get]
|
// @Router /debug/health/settings [get]
|
||||||
func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request) {
|
||||||
settingsJSON, err := api.Database.GetHealthSettings(r.Context())
|
settingsJSON, err := api.Database.GetHealthSettings(r.Context())
|
||||||
|
@ -164,7 +164,7 @@ func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings codersdk.HealthSettings
|
var settings healthsdk.HealthSettings
|
||||||
err = json.Unmarshal([]byte(settingsJSON), &settings)
|
err = json.Unmarshal([]byte(settingsJSON), &settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
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 {
|
if len(settings.DismissedHealthchecks) == 0 {
|
||||||
settings.DismissedHealthchecks = []codersdk.HealthSection{}
|
settings.DismissedHealthchecks = []healthsdk.HealthSection{}
|
||||||
}
|
}
|
||||||
|
|
||||||
httpapi.Write(r.Context(), rw, http.StatusOK, settings)
|
httpapi.Write(r.Context(), rw, http.StatusOK, settings)
|
||||||
|
@ -187,8 +187,8 @@ func (api *API) deploymentHealthSettings(rw http.ResponseWriter, r *http.Request
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Tags Debug
|
// @Tags Debug
|
||||||
// @Param request body codersdk.UpdateHealthSettings true "Update health settings"
|
// @Param request body healthsdk.UpdateHealthSettings true "Update health settings"
|
||||||
// @Success 200 {object} codersdk.UpdateHealthSettings
|
// @Success 200 {object} healthsdk.UpdateHealthSettings
|
||||||
// @Router /debug/health/settings [put]
|
// @Router /debug/health/settings [put]
|
||||||
func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
@ -200,7 +200,7 @@ func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Requ
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings codersdk.HealthSettings
|
var settings healthsdk.HealthSettings
|
||||||
if !httpapi.Read(ctx, rw, r, &settings) {
|
if !httpapi.Read(ctx, rw, r, &settings) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -264,9 +264,9 @@ func (api *API) putDeploymentHealthSettings(rw http.ResponseWriter, r *http.Requ
|
||||||
httpapi.Write(r.Context(), rw, http.StatusOK, settings)
|
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 {
|
for _, dismissed := range settings.DismissedHealthchecks {
|
||||||
ok := slices.Contains(codersdk.HealthSections, dismissed)
|
ok := slices.Contains(healthsdk.HealthSections, dismissed)
|
||||||
if !ok {
|
if !ok {
|
||||||
return xerrors.Errorf("unknown healthcheck section: %s", dismissed)
|
return xerrors.Errorf("unknown healthcheck section: %s", dismissed)
|
||||||
}
|
}
|
||||||
|
@ -306,11 +306,11 @@ func _debugDERPTraffic(http.ResponseWriter, *http.Request) {} //nolint:unused
|
||||||
// @x-apidocgen {"skip": true}
|
// @x-apidocgen {"skip": true}
|
||||||
func _debugExpVar(http.ResponseWriter, *http.Request) {} //nolint:unused
|
func _debugExpVar(http.ResponseWriter, *http.Request) {} //nolint:unused
|
||||||
|
|
||||||
func loadDismissedHealthchecks(ctx context.Context, db database.Store, logger slog.Logger) []codersdk.HealthSection {
|
func loadDismissedHealthchecks(ctx context.Context, db database.Store, logger slog.Logger) []healthsdk.HealthSection {
|
||||||
dismissedHealthchecks := []codersdk.HealthSection{}
|
dismissedHealthchecks := []healthsdk.HealthSection{}
|
||||||
settingsJSON, err := db.GetHealthSettings(ctx)
|
settingsJSON, err := db.GetHealthSettings(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var settings codersdk.HealthSettings
|
var settings healthsdk.HealthSettings
|
||||||
err = json.Unmarshal([]byte(settingsJSON), &settings)
|
err = json.Unmarshal([]byte(settingsJSON), &settings)
|
||||||
if len(settings.DismissedHealthchecks) > 0 {
|
if len(settings.DismissedHealthchecks) > 0 {
|
||||||
dismissedHealthchecks = settings.DismissedHealthchecks
|
dismissedHealthchecks = settings.DismissedHealthchecks
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"cdr.dev/slog/sloggers/slogtest"
|
"cdr.dev/slog/sloggers/slogtest"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
"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"
|
"github.com/coder/coder/v2/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,10 +29,10 @@ func TestDebugHealth(t *testing.T) {
|
||||||
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
|
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||||
sessionToken string
|
sessionToken string
|
||||||
client = coderdtest.New(t, &coderdtest.Options{
|
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)
|
calls.Add(1)
|
||||||
assert.Equal(t, sessionToken, apiKey)
|
assert.Equal(t, sessionToken, apiKey)
|
||||||
return &codersdk.HealthcheckReport{
|
return &healthsdk.HealthcheckReport{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -62,10 +62,10 @@ func TestDebugHealth(t *testing.T) {
|
||||||
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
|
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||||
sessionToken string
|
sessionToken string
|
||||||
client = coderdtest.New(t, &coderdtest.Options{
|
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)
|
calls.Add(1)
|
||||||
assert.Equal(t, sessionToken, apiKey)
|
assert.Equal(t, sessionToken, apiKey)
|
||||||
return &codersdk.HealthcheckReport{
|
return &healthsdk.HealthcheckReport{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -97,15 +97,15 @@ func TestDebugHealth(t *testing.T) {
|
||||||
client = coderdtest.New(t, &coderdtest.Options{
|
client = coderdtest.New(t, &coderdtest.Options{
|
||||||
Logger: &logger,
|
Logger: &logger,
|
||||||
HealthcheckTimeout: time.Microsecond,
|
HealthcheckTimeout: time.Microsecond,
|
||||||
HealthcheckFunc: func(context.Context, string) *codersdk.HealthcheckReport {
|
HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport {
|
||||||
t := time.NewTimer(time.Second)
|
t := time.NewTimer(time.Second)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return &codersdk.HealthcheckReport{}
|
return &healthsdk.HealthcheckReport{}
|
||||||
case <-t.C:
|
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)
|
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||||
client = coderdtest.New(t, &coderdtest.Options{
|
client = coderdtest.New(t, &coderdtest.Options{
|
||||||
HealthcheckRefresh: time.Microsecond,
|
HealthcheckRefresh: time.Microsecond,
|
||||||
HealthcheckFunc: func(context.Context, string) *codersdk.HealthcheckReport {
|
HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport {
|
||||||
calls <- struct{}{}
|
calls <- struct{}{}
|
||||||
return &codersdk.HealthcheckReport{}
|
return &healthsdk.HealthcheckReport{}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
_ = coderdtest.CreateFirstUser(t, client)
|
_ = coderdtest.CreateFirstUser(t, client)
|
||||||
|
@ -174,9 +174,9 @@ func TestDebugHealth(t *testing.T) {
|
||||||
client = coderdtest.New(t, &coderdtest.Options{
|
client = coderdtest.New(t, &coderdtest.Options{
|
||||||
HealthcheckRefresh: time.Hour,
|
HealthcheckRefresh: time.Hour,
|
||||||
HealthcheckTimeout: time.Hour,
|
HealthcheckTimeout: time.Hour,
|
||||||
HealthcheckFunc: func(context.Context, string) *codersdk.HealthcheckReport {
|
HealthcheckFunc: func(context.Context, string) *healthsdk.HealthcheckReport {
|
||||||
calls++
|
calls++
|
||||||
return &codersdk.HealthcheckReport{
|
return &healthsdk.HealthcheckReport{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -208,12 +208,12 @@ func TestDebugHealth(t *testing.T) {
|
||||||
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
|
ctx, cancel = context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||||
sessionToken string
|
sessionToken string
|
||||||
client = coderdtest.New(t, &coderdtest.Options{
|
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)
|
assert.Equal(t, sessionToken, apiKey)
|
||||||
return &codersdk.HealthcheckReport{
|
return &healthsdk.HealthcheckReport{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Healthy: true,
|
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)
|
_ = coderdtest.CreateFirstUser(t, adminClient)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
settings, err := adminClient.HealthSettings(ctx)
|
settings, err := healthsdk.New(adminClient).HealthSettings(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// then
|
// 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) {
|
t.Run("DismissSection", func(t *testing.T) {
|
||||||
|
@ -268,16 +268,16 @@ func TestHealthSettings(t *testing.T) {
|
||||||
adminClient := coderdtest.New(t, nil)
|
adminClient := coderdtest.New(t, nil)
|
||||||
_ = coderdtest.CreateFirstUser(t, adminClient)
|
_ = coderdtest.CreateFirstUser(t, adminClient)
|
||||||
|
|
||||||
expected := codersdk.HealthSettings{
|
expected := healthsdk.HealthSettings{
|
||||||
DismissedHealthchecks: []codersdk.HealthSection{codersdk.HealthSectionDERP, codersdk.HealthSectionWebsocket},
|
DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket},
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: dismiss "derp" and "websocket"
|
// when: dismiss "derp" and "websocket"
|
||||||
err := adminClient.PutHealthSettings(ctx, expected)
|
err := healthsdk.New(adminClient).PutHealthSettings(ctx, expected)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
settings, err := adminClient.HealthSettings(ctx)
|
settings, err := healthsdk.New(adminClient).HealthSettings(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, settings)
|
require.Equal(t, expected, settings)
|
||||||
|
|
||||||
|
@ -287,7 +287,7 @@ func TestHealthSettings(t *testing.T) {
|
||||||
bs, err := io.ReadAll(res.Body)
|
bs, err := io.ReadAll(res.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
var hc codersdk.HealthcheckReport
|
var hc healthsdk.HealthcheckReport
|
||||||
require.NoError(t, json.Unmarshal(bs, &hc))
|
require.NoError(t, json.Unmarshal(bs, &hc))
|
||||||
require.True(t, hc.DERP.Dismissed)
|
require.True(t, hc.DERP.Dismissed)
|
||||||
require.True(t, hc.Websocket.Dismissed)
|
require.True(t, hc.Websocket.Dismissed)
|
||||||
|
@ -303,23 +303,23 @@ func TestHealthSettings(t *testing.T) {
|
||||||
adminClient := coderdtest.New(t, nil)
|
adminClient := coderdtest.New(t, nil)
|
||||||
_ = coderdtest.CreateFirstUser(t, adminClient)
|
_ = coderdtest.CreateFirstUser(t, adminClient)
|
||||||
|
|
||||||
initial := codersdk.HealthSettings{
|
initial := healthsdk.HealthSettings{
|
||||||
DismissedHealthchecks: []codersdk.HealthSection{codersdk.HealthSectionDERP, codersdk.HealthSectionWebsocket},
|
DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := adminClient.PutHealthSettings(ctx, initial)
|
err := healthsdk.New(adminClient).PutHealthSettings(ctx, initial)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
expected := codersdk.HealthSettings{
|
expected := healthsdk.HealthSettings{
|
||||||
DismissedHealthchecks: []codersdk.HealthSection{codersdk.HealthSectionDERP},
|
DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP},
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: undismiss "websocket"
|
// when: undismiss "websocket"
|
||||||
err = adminClient.PutHealthSettings(ctx, expected)
|
err = healthsdk.New(adminClient).PutHealthSettings(ctx, expected)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
settings, err := adminClient.HealthSettings(ctx)
|
settings, err := healthsdk.New(adminClient).HealthSettings(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected, settings)
|
require.Equal(t, expected, settings)
|
||||||
|
|
||||||
|
@ -329,7 +329,7 @@ func TestHealthSettings(t *testing.T) {
|
||||||
bs, err := io.ReadAll(res.Body)
|
bs, err := io.ReadAll(res.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
var hc codersdk.HealthcheckReport
|
var hc healthsdk.HealthcheckReport
|
||||||
require.NoError(t, json.Unmarshal(bs, &hc))
|
require.NoError(t, json.Unmarshal(bs, &hc))
|
||||||
require.True(t, hc.DERP.Dismissed)
|
require.True(t, hc.DERP.Dismissed)
|
||||||
require.False(t, hc.Websocket.Dismissed)
|
require.False(t, hc.Websocket.Dismissed)
|
||||||
|
@ -345,15 +345,15 @@ func TestHealthSettings(t *testing.T) {
|
||||||
adminClient := coderdtest.New(t, nil)
|
adminClient := coderdtest.New(t, nil)
|
||||||
_ = coderdtest.CreateFirstUser(t, adminClient)
|
_ = coderdtest.CreateFirstUser(t, adminClient)
|
||||||
|
|
||||||
expected := codersdk.HealthSettings{
|
expected := healthsdk.HealthSettings{
|
||||||
DismissedHealthchecks: []codersdk.HealthSection{codersdk.HealthSectionDERP, codersdk.HealthSectionWebsocket},
|
DismissedHealthchecks: []healthsdk.HealthSection{healthsdk.HealthSectionDERP, healthsdk.HealthSectionWebsocket},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := adminClient.PutHealthSettings(ctx, expected)
|
err := healthsdk.New(adminClient).PutHealthSettings(ctx, expected)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
err = adminClient.PutHealthSettings(ctx, expected)
|
err = healthsdk.New(adminClient).PutHealthSettings(ctx, expected)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"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 {
|
type AccessURLReportOptions struct {
|
||||||
AccessURL *url.URL
|
AccessURL *url.URL
|
||||||
|
|
|
@ -8,14 +8,14 @@ import (
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk/healthsdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DatabaseDefaultThreshold = 15 * time.Millisecond
|
DatabaseDefaultThreshold = 15 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
type DatabaseReport codersdk.DatabaseReport
|
type DatabaseReport healthsdk.DatabaseReport
|
||||||
|
|
||||||
type DatabaseReportOptions struct {
|
type DatabaseReportOptions struct {
|
||||||
DB database.Store
|
DB database.Store
|
||||||
|
|
|
@ -25,7 +25,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||||
"github.com/coder/coder/v2/coderd/util/slice"
|
"github.com/coder/coder/v2/coderd/util/slice"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk/healthsdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -40,15 +40,15 @@ type ReportOptions struct {
|
||||||
DERPMap *tailcfg.DERPMap
|
DERPMap *tailcfg.DERPMap
|
||||||
}
|
}
|
||||||
|
|
||||||
type Report codersdk.DERPHealthReport
|
type Report healthsdk.DERPHealthReport
|
||||||
|
|
||||||
type RegionReport struct {
|
type RegionReport struct {
|
||||||
codersdk.DERPRegionReport
|
healthsdk.DERPRegionReport
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeReport struct {
|
type NodeReport struct {
|
||||||
codersdk.DERPNodeReport
|
healthsdk.DERPNodeReport
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
clientCounter int
|
clientCounter int
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
|
||||||
r.Warnings = []health.Message{}
|
r.Warnings = []health.Message{}
|
||||||
r.Dismissed = opts.Dismissed
|
r.Dismissed = opts.Dismissed
|
||||||
|
|
||||||
r.Regions = map[int]*codersdk.DERPRegionReport{}
|
r.Regions = map[int]*healthsdk.DERPRegionReport{}
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
mu := sync.Mutex{}
|
mu := sync.Mutex{}
|
||||||
|
@ -69,7 +69,7 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
|
||||||
var (
|
var (
|
||||||
region = region
|
region = region
|
||||||
regionReport = RegionReport{
|
regionReport = RegionReport{
|
||||||
DERPRegionReport: codersdk.DERPRegionReport{
|
DERPRegionReport: healthsdk.DERPRegionReport{
|
||||||
Region: region,
|
Region: region,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ func (r *Report) Run(ctx context.Context, opts *ReportOptions) {
|
||||||
func (r *RegionReport) Run(ctx context.Context) {
|
func (r *RegionReport) Run(ctx context.Context) {
|
||||||
r.Healthy = true
|
r.Healthy = true
|
||||||
r.Severity = health.SeverityOK
|
r.Severity = health.SeverityOK
|
||||||
r.NodeReports = []*codersdk.DERPNodeReport{}
|
r.NodeReports = []*healthsdk.DERPNodeReport{}
|
||||||
r.Warnings = []health.Message{}
|
r.Warnings = []health.Message{}
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
|
@ -132,7 +132,7 @@ func (r *RegionReport) Run(ctx context.Context) {
|
||||||
var (
|
var (
|
||||||
node = node
|
node = node
|
||||||
nodeReport = NodeReport{
|
nodeReport = NodeReport{
|
||||||
DERPNodeReport: codersdk.DERPNodeReport{
|
DERPNodeReport: healthsdk.DERPNodeReport{
|
||||||
Node: node,
|
Node: node,
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
},
|
},
|
||||||
|
@ -499,8 +499,8 @@ func convertError(err error) *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortNodeReports(reports []*codersdk.DERPNodeReport) {
|
func sortNodeReports(reports []*healthsdk.DERPNodeReport) {
|
||||||
slices.SortFunc(reports, func(a, b *codersdk.DERPNodeReport) int {
|
slices.SortFunc(reports, func(a, b *healthsdk.DERPNodeReport) int {
|
||||||
return slice.Ascending(a.Node.Name, b.Node.Name)
|
return slice.Ascending(a.Node.Name, b.Node.Name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,16 +9,16 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk/healthsdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Checker interface {
|
type Checker interface {
|
||||||
DERP(ctx context.Context, opts *derphealth.ReportOptions) codersdk.DERPHealthReport
|
DERP(ctx context.Context, opts *derphealth.ReportOptions) healthsdk.DERPHealthReport
|
||||||
AccessURL(ctx context.Context, opts *AccessURLReportOptions) codersdk.AccessURLReport
|
AccessURL(ctx context.Context, opts *AccessURLReportOptions) healthsdk.AccessURLReport
|
||||||
Websocket(ctx context.Context, opts *WebsocketReportOptions) codersdk.WebsocketReport
|
Websocket(ctx context.Context, opts *WebsocketReportOptions) healthsdk.WebsocketReport
|
||||||
Database(ctx context.Context, opts *DatabaseReportOptions) codersdk.DatabaseReport
|
Database(ctx context.Context, opts *DatabaseReportOptions) healthsdk.DatabaseReport
|
||||||
WorkspaceProxy(ctx context.Context, opts *WorkspaceProxyReportOptions) codersdk.WorkspaceProxyReport
|
WorkspaceProxy(ctx context.Context, opts *WorkspaceProxyReportOptions) healthsdk.WorkspaceProxyReport
|
||||||
ProvisionerDaemons(ctx context.Context, opts *ProvisionerDaemonsReportDeps) codersdk.ProvisionerDaemonsReport
|
ProvisionerDaemons(ctx context.Context, opts *ProvisionerDaemonsReportDeps) healthsdk.ProvisionerDaemonsReport
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReportOptions struct {
|
type ReportOptions struct {
|
||||||
|
@ -34,46 +34,46 @@ type ReportOptions struct {
|
||||||
|
|
||||||
type defaultChecker 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
|
var report derphealth.Report
|
||||||
report.Run(ctx, opts)
|
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
|
var report AccessURLReport
|
||||||
report.Run(ctx, opts)
|
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
|
var report WebsocketReport
|
||||||
report.Run(ctx, opts)
|
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
|
var report DatabaseReport
|
||||||
report.Run(ctx, opts)
|
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
|
var report WorkspaceProxyReport
|
||||||
report.Run(ctx, opts)
|
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
|
var report ProvisionerDaemonsReport
|
||||||
report.Run(ctx, opts)
|
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 (
|
var (
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
report codersdk.HealthcheckReport
|
report healthsdk.HealthcheckReport
|
||||||
)
|
)
|
||||||
|
|
||||||
if opts.Checker == nil {
|
if opts.Checker == nil {
|
||||||
|
@ -156,24 +156,24 @@ func Run(ctx context.Context, opts *ReportOptions) *codersdk.HealthcheckReport {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
report.Time = time.Now()
|
report.Time = time.Now()
|
||||||
report.FailingSections = []codersdk.HealthSection{}
|
report.FailingSections = []healthsdk.HealthSection{}
|
||||||
if report.DERP.Severity.Value() > health.SeverityWarning.Value() {
|
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() {
|
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() {
|
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() {
|
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() {
|
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() {
|
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
|
report.Healthy = len(report.FailingSections) == 0
|
||||||
|
|
|
@ -9,39 +9,39 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck"
|
"github.com/coder/coder/v2/coderd/healthcheck"
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
"github.com/coder/coder/v2/coderd/healthcheck/derphealth"
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk/healthsdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testChecker struct {
|
type testChecker struct {
|
||||||
DERPReport codersdk.DERPHealthReport
|
DERPReport healthsdk.DERPHealthReport
|
||||||
AccessURLReport codersdk.AccessURLReport
|
AccessURLReport healthsdk.AccessURLReport
|
||||||
WebsocketReport codersdk.WebsocketReport
|
WebsocketReport healthsdk.WebsocketReport
|
||||||
DatabaseReport codersdk.DatabaseReport
|
DatabaseReport healthsdk.DatabaseReport
|
||||||
WorkspaceProxyReport codersdk.WorkspaceProxyReport
|
WorkspaceProxyReport healthsdk.WorkspaceProxyReport
|
||||||
ProvisionerDaemonsReport codersdk.ProvisionerDaemonsReport
|
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
|
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
|
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
|
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
|
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
|
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
|
return c.ProvisionerDaemonsReport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,346 +53,346 @@ func TestHealthcheck(t *testing.T) {
|
||||||
checker *testChecker
|
checker *testChecker
|
||||||
healthy bool
|
healthy bool
|
||||||
severity health.Severity
|
severity health.Severity
|
||||||
failingSections []codersdk.HealthSection
|
failingSections []healthsdk.HealthSection
|
||||||
}{{
|
}{{
|
||||||
name: "OK",
|
name: "OK",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
healthy: true,
|
healthy: true,
|
||||||
severity: health.SeverityOK,
|
severity: health.SeverityOK,
|
||||||
failingSections: []codersdk.HealthSection{},
|
failingSections: []healthsdk.HealthSection{},
|
||||||
}, {
|
}, {
|
||||||
name: "DERPFail",
|
name: "DERPFail",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
healthy: false,
|
healthy: false,
|
||||||
severity: health.SeverityError,
|
severity: health.SeverityError,
|
||||||
failingSections: []codersdk.HealthSection{codersdk.HealthSectionDERP},
|
failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionDERP},
|
||||||
}, {
|
}, {
|
||||||
name: "DERPWarning",
|
name: "DERPWarning",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}},
|
Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}},
|
||||||
Severity: health.SeverityWarning,
|
Severity: health.SeverityWarning,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
healthy: true,
|
healthy: true,
|
||||||
severity: health.SeverityWarning,
|
severity: health.SeverityWarning,
|
||||||
failingSections: []codersdk.HealthSection{},
|
failingSections: []healthsdk.HealthSection{},
|
||||||
}, {
|
}, {
|
||||||
name: "AccessURLFail",
|
name: "AccessURLFail",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityWarning,
|
Severity: health.SeverityWarning,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
healthy: false,
|
healthy: false,
|
||||||
severity: health.SeverityWarning,
|
severity: health.SeverityWarning,
|
||||||
failingSections: []codersdk.HealthSection{codersdk.HealthSectionAccessURL},
|
failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionAccessURL},
|
||||||
}, {
|
}, {
|
||||||
name: "WebsocketFail",
|
name: "WebsocketFail",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
healthy: false,
|
healthy: false,
|
||||||
severity: health.SeverityError,
|
severity: health.SeverityError,
|
||||||
failingSections: []codersdk.HealthSection{codersdk.HealthSectionWebsocket},
|
failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionWebsocket},
|
||||||
}, {
|
}, {
|
||||||
name: "DatabaseFail",
|
name: "DatabaseFail",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
healthy: false,
|
healthy: false,
|
||||||
severity: health.SeverityError,
|
severity: health.SeverityError,
|
||||||
failingSections: []codersdk.HealthSection{codersdk.HealthSectionDatabase},
|
failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionDatabase},
|
||||||
}, {
|
}, {
|
||||||
name: "ProxyFail",
|
name: "ProxyFail",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
severity: health.SeverityError,
|
severity: health.SeverityError,
|
||||||
healthy: false,
|
healthy: false,
|
||||||
failingSections: []codersdk.HealthSection{codersdk.HealthSectionWorkspaceProxy},
|
failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionWorkspaceProxy},
|
||||||
}, {
|
}, {
|
||||||
name: "ProxyWarn",
|
name: "ProxyWarn",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}},
|
Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}},
|
||||||
Severity: health.SeverityWarning,
|
Severity: health.SeverityWarning,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
severity: health.SeverityWarning,
|
severity: health.SeverityWarning,
|
||||||
healthy: true,
|
healthy: true,
|
||||||
failingSections: []codersdk.HealthSection{},
|
failingSections: []healthsdk.HealthSection{},
|
||||||
}, {
|
}, {
|
||||||
name: "ProvisionerDaemonsFail",
|
name: "ProvisionerDaemonsFail",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
severity: health.SeverityError,
|
severity: health.SeverityError,
|
||||||
healthy: false,
|
healthy: false,
|
||||||
failingSections: []codersdk.HealthSection{codersdk.HealthSectionProvisionerDaemons},
|
failingSections: []healthsdk.HealthSection{healthsdk.HealthSectionProvisionerDaemons},
|
||||||
}, {
|
}, {
|
||||||
name: "ProvisionerDaemonsWarn",
|
name: "ProvisionerDaemonsWarn",
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: true,
|
Healthy: true,
|
||||||
Severity: health.SeverityOK,
|
Severity: health.SeverityOK,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityWarning,
|
Severity: health.SeverityWarning,
|
||||||
Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}},
|
Warnings: []health.Message{{Message: "foobar", Code: "EFOOBAR"}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
severity: health.SeverityWarning,
|
severity: health.SeverityWarning,
|
||||||
healthy: true,
|
healthy: true,
|
||||||
failingSections: []codersdk.HealthSection{},
|
failingSections: []healthsdk.HealthSection{},
|
||||||
}, {
|
}, {
|
||||||
name: "AllFail",
|
name: "AllFail",
|
||||||
healthy: false,
|
healthy: false,
|
||||||
checker: &testChecker{
|
checker: &testChecker{
|
||||||
DERPReport: codersdk.DERPHealthReport{
|
DERPReport: healthsdk.DERPHealthReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
AccessURLReport: codersdk.AccessURLReport{
|
AccessURLReport: healthsdk.AccessURLReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
WebsocketReport: codersdk.WebsocketReport{
|
WebsocketReport: healthsdk.WebsocketReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
DatabaseReport: codersdk.DatabaseReport{
|
DatabaseReport: healthsdk.DatabaseReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
WorkspaceProxyReport: codersdk.WorkspaceProxyReport{
|
WorkspaceProxyReport: healthsdk.WorkspaceProxyReport{
|
||||||
Healthy: false,
|
Healthy: false,
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
ProvisionerDaemonsReport: codersdk.ProvisionerDaemonsReport{
|
ProvisionerDaemonsReport: healthsdk.ProvisionerDaemonsReport{
|
||||||
Severity: health.SeverityError,
|
Severity: health.SeverityError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
severity: health.SeverityError,
|
severity: health.SeverityError,
|
||||||
failingSections: []codersdk.HealthSection{
|
failingSections: []healthsdk.HealthSection{
|
||||||
codersdk.HealthSectionDERP,
|
healthsdk.HealthSectionDERP,
|
||||||
codersdk.HealthSectionAccessURL,
|
healthsdk.HealthSectionAccessURL,
|
||||||
codersdk.HealthSectionWebsocket,
|
healthsdk.HealthSectionWebsocket,
|
||||||
codersdk.HealthSectionDatabase,
|
healthsdk.HealthSectionDatabase,
|
||||||
codersdk.HealthSectionWorkspaceProxy,
|
healthsdk.HealthSectionWorkspaceProxy,
|
||||||
codersdk.HealthSectionProvisionerDaemons,
|
healthsdk.HealthSectionProvisionerDaemons,
|
||||||
},
|
},
|
||||||
}} {
|
}} {
|
||||||
c := c
|
c := c
|
||||||
|
|
|
@ -16,11 +16,11 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||||
"github.com/coder/coder/v2/coderd/provisionerdserver"
|
"github.com/coder/coder/v2/coderd/provisionerdserver"
|
||||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
"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"
|
"github.com/coder/coder/v2/provisionerd/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProvisionerDaemonsReport codersdk.ProvisionerDaemonsReport
|
type ProvisionerDaemonsReport healthsdk.ProvisionerDaemonsReport
|
||||||
|
|
||||||
type ProvisionerDaemonsReportDeps struct {
|
type ProvisionerDaemonsReportDeps struct {
|
||||||
// Required
|
// Required
|
||||||
|
@ -40,7 +40,7 @@ type ProvisionerDaemonsStore interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProvisionerDaemonsReport) Run(ctx context.Context, opts *ProvisionerDaemonsReportDeps) {
|
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.Severity = health.SeverityOK
|
||||||
r.Warnings = make([]health.Message, 0)
|
r.Warnings = make([]health.Message, 0)
|
||||||
r.Dismissed = opts.Dismissed
|
r.Dismissed = opts.Dismissed
|
||||||
|
@ -95,7 +95,7 @@ func (r *ProvisionerDaemonsReport) Run(ctx context.Context, opts *ProvisionerDae
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
it := codersdk.ProvisionerDaemonsReportItem{
|
it := healthsdk.ProvisionerDaemonsReportItem{
|
||||||
ProvisionerDaemon: db2sdk.ProvisionerDaemon(daemon),
|
ProvisionerDaemon: db2sdk.ProvisionerDaemon(daemon),
|
||||||
Warnings: make([]health.Message, 0),
|
Warnings: make([]health.Message, 0),
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"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"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbmock"
|
"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"
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/healthsdk"
|
||||||
"github.com/coder/coder/v2/provisionerd/proto"
|
"github.com/coder/coder/v2/provisionerd/proto"
|
||||||
|
|
||||||
gomock "go.uber.org/mock/gomock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProvisionerDaemonReport(t *testing.T) {
|
func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
|
@ -34,21 +34,21 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
expectedSeverity health.Severity
|
expectedSeverity health.Severity
|
||||||
expectedWarningCode health.Code
|
expectedWarningCode health.Code
|
||||||
expectedError string
|
expectedError string
|
||||||
expectedItems []codersdk.ProvisionerDaemonsReportItem
|
expectedItems []healthsdk.ProvisionerDaemonsReportItem
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "current version empty",
|
name: "current version empty",
|
||||||
currentVersion: "",
|
currentVersion: "",
|
||||||
expectedSeverity: health.SeverityError,
|
expectedSeverity: health.SeverityError,
|
||||||
expectedError: "Developer error: CurrentVersion is empty",
|
expectedError: "Developer error: CurrentVersion is empty",
|
||||||
expectedItems: []codersdk.ProvisionerDaemonsReportItem{},
|
expectedItems: []healthsdk.ProvisionerDaemonsReportItem{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no daemons",
|
name: "no daemons",
|
||||||
currentVersion: "v1.2.3",
|
currentVersion: "v1.2.3",
|
||||||
currentAPIMajorVersion: proto.CurrentMajor,
|
currentAPIMajorVersion: proto.CurrentMajor,
|
||||||
expectedSeverity: health.SeverityError,
|
expectedSeverity: health.SeverityError,
|
||||||
expectedItems: []codersdk.ProvisionerDaemonsReportItem{},
|
expectedItems: []healthsdk.ProvisionerDaemonsReportItem{},
|
||||||
expectedWarningCode: health.CodeProvisionerDaemonsNoProvisionerDaemons,
|
expectedWarningCode: health.CodeProvisionerDaemonsNoProvisionerDaemons,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -58,7 +58,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
provisionerDaemonsErr: assert.AnError,
|
provisionerDaemonsErr: assert.AnError,
|
||||||
expectedSeverity: health.SeverityError,
|
expectedSeverity: health.SeverityError,
|
||||||
expectedError: assert.AnError.Error(),
|
expectedError: assert.AnError.Error(),
|
||||||
expectedItems: []codersdk.ProvisionerDaemonsReportItem{},
|
expectedItems: []healthsdk.ProvisionerDaemonsReportItem{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one daemon up to date",
|
name: "one daemon up to date",
|
||||||
|
@ -66,7 +66,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
currentAPIMajorVersion: proto.CurrentMajor,
|
currentAPIMajorVersion: proto.CurrentMajor,
|
||||||
expectedSeverity: health.SeverityOK,
|
expectedSeverity: health.SeverityOK,
|
||||||
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-ok", "v1.2.3", "1.0", now)},
|
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-ok", "v1.2.3", "1.0", now)},
|
||||||
expectedItems: []codersdk.ProvisionerDaemonsReportItem{
|
expectedItems: []healthsdk.ProvisionerDaemonsReportItem{
|
||||||
{
|
{
|
||||||
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
|
@ -89,7 +89,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
expectedSeverity: health.SeverityWarning,
|
expectedSeverity: health.SeverityWarning,
|
||||||
expectedWarningCode: health.CodeProvisionerDaemonVersionMismatch,
|
expectedWarningCode: health.CodeProvisionerDaemonVersionMismatch,
|
||||||
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-old", "v1.1.2", "1.0", now)},
|
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-old", "v1.1.2", "1.0", now)},
|
||||||
expectedItems: []codersdk.ProvisionerDaemonsReportItem{
|
expectedItems: []healthsdk.ProvisionerDaemonsReportItem{
|
||||||
{
|
{
|
||||||
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
|
@ -117,7 +117,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
expectedSeverity: health.SeverityError,
|
expectedSeverity: health.SeverityError,
|
||||||
expectedWarningCode: health.CodeUnknown,
|
expectedWarningCode: health.CodeUnknown,
|
||||||
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-invalid-version", "invalid", "1.0", now)},
|
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-invalid-version", "invalid", "1.0", now)},
|
||||||
expectedItems: []codersdk.ProvisionerDaemonsReportItem{
|
expectedItems: []healthsdk.ProvisionerDaemonsReportItem{
|
||||||
{
|
{
|
||||||
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
|
@ -145,7 +145,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
expectedSeverity: health.SeverityError,
|
expectedSeverity: health.SeverityError,
|
||||||
expectedWarningCode: health.CodeUnknown,
|
expectedWarningCode: health.CodeUnknown,
|
||||||
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-invalid-api", "v1.2.3", "invalid", now)},
|
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-invalid-api", "v1.2.3", "invalid", now)},
|
||||||
expectedItems: []codersdk.ProvisionerDaemonsReportItem{
|
expectedItems: []healthsdk.ProvisionerDaemonsReportItem{
|
||||||
{
|
{
|
||||||
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
|
@ -173,7 +173,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
expectedSeverity: health.SeverityWarning,
|
expectedSeverity: health.SeverityWarning,
|
||||||
expectedWarningCode: health.CodeProvisionerDaemonAPIMajorVersionDeprecated,
|
expectedWarningCode: health.CodeProvisionerDaemonAPIMajorVersionDeprecated,
|
||||||
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-old-api", "v2.3.4", "1.0", now)},
|
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemon(t, "pd-old-api", "v2.3.4", "1.0", now)},
|
||||||
expectedItems: []codersdk.ProvisionerDaemonsReportItem{
|
expectedItems: []healthsdk.ProvisionerDaemonsReportItem{
|
||||||
{
|
{
|
||||||
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
|
@ -201,7 +201,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
expectedSeverity: health.SeverityWarning,
|
expectedSeverity: health.SeverityWarning,
|
||||||
expectedWarningCode: health.CodeProvisionerDaemonVersionMismatch,
|
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)},
|
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{
|
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
|
@ -242,7 +242,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
expectedSeverity: health.SeverityWarning,
|
expectedSeverity: health.SeverityWarning,
|
||||||
expectedWarningCode: health.CodeProvisionerDaemonVersionMismatch,
|
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)},
|
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{
|
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
|
@ -282,7 +282,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
currentAPIMajorVersion: proto.CurrentMajor,
|
currentAPIMajorVersion: proto.CurrentMajor,
|
||||||
expectedSeverity: health.SeverityOK,
|
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)},
|
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{
|
ProvisionerDaemon: codersdk.ProvisionerDaemon{
|
||||||
ID: uuid.Nil,
|
ID: uuid.Nil,
|
||||||
|
@ -305,7 +305,7 @@ func TestProvisionerDaemonReport(t *testing.T) {
|
||||||
expectedSeverity: health.SeverityError,
|
expectedSeverity: health.SeverityError,
|
||||||
expectedWarningCode: health.CodeProvisionerDaemonsNoProvisionerDaemons,
|
expectedWarningCode: health.CodeProvisionerDaemonsNoProvisionerDaemons,
|
||||||
provisionerDaemons: []database.ProvisionerDaemon{fakeProvisionerDaemonStale(t, "pd-ok", "v1.2.3", "0.9", now.Add(-5*time.Minute), now)},
|
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
|
tt := tt
|
||||||
|
|
|
@ -13,10 +13,10 @@ import (
|
||||||
"nhooyr.io/websocket"
|
"nhooyr.io/websocket"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"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 {
|
type WebsocketReportOptions struct {
|
||||||
APIKey string
|
APIKey string
|
||||||
|
|
|
@ -9,9 +9,10 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/healthsdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkspaceProxyReport codersdk.WorkspaceProxyReport
|
type WorkspaceProxyReport healthsdk.WorkspaceProxyReport
|
||||||
|
|
||||||
type WorkspaceProxyReportOptions struct {
|
type WorkspaceProxyReportOptions struct {
|
||||||
WorkspaceProxiesFetchUpdater WorkspaceProxiesFetchUpdater
|
WorkspaceProxiesFetchUpdater WorkspaceProxiesFetchUpdater
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/workspaceapps"
|
"github.com/coder/coder/v2/coderd/workspaceapps"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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/provisioner/echo"
|
||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
|
@ -86,9 +87,10 @@ func TestDeploymentInsights(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotZero(t, res.Workspaces[0].LastUsedAt)
|
assert.NotZero(t, res.Workspaces[0].LastUsedAt)
|
||||||
|
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: slogtest.Make(t, nil).Named("tailnet"),
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: slogtest.Make(t, nil).Named("tailnet"),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
@ -174,9 +176,10 @@ func TestUserActivityInsights_SanityCheck(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Connect to the agent to generate usage/latency stats.
|
// Connect to the agent to generate usage/latency stats.
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: logger.Named("client"),
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: logger.Named("client"),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
@ -271,9 +274,10 @@ func TestUserLatencyInsights(t *testing.T) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Connect to the agent to generate usage/latency stats.
|
// Connect to the agent to generate usage/latency stats.
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: logger.Named("client"),
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: logger.Named("client"),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"github.com/coder/coder/v2/coderd/tracing"
|
"github.com/coder/coder/v2/coderd/tracing"
|
||||||
"github.com/coder/coder/v2/coderd/workspaceapps"
|
"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/site"
|
||||||
"github.com/coder/coder/v2/tailnet"
|
"github.com/coder/coder/v2/tailnet"
|
||||||
"github.com/coder/retry"
|
"github.com/coder/retry"
|
||||||
|
@ -427,9 +427,9 @@ func (s *ServerTailnet) acquireTicket(agentID uuid.UUID) (release func()) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*codersdk.WorkspaceAgentConn, func(), error) {
|
func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*workspacesdk.AgentConn, func(), error) {
|
||||||
var (
|
var (
|
||||||
conn *codersdk.WorkspaceAgentConn
|
conn *workspacesdk.AgentConn
|
||||||
ret func()
|
ret func()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -440,9 +440,9 @@ func (s *ServerTailnet) AgentConn(ctx context.Context, agentID uuid.UUID) (*code
|
||||||
}
|
}
|
||||||
ret = s.acquireTicket(agentID)
|
ret = s.acquireTicket(agentID)
|
||||||
|
|
||||||
conn = codersdk.NewWorkspaceAgentConn(s.conn, codersdk.WorkspaceAgentConnOptions{
|
conn = workspacesdk.NewAgentConn(s.conn, workspacesdk.AgentConnOptions{
|
||||||
AgentID: agentID,
|
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
|
// Since we now have an open conn, be careful to close it if we error
|
||||||
|
|
|
@ -26,8 +26,8 @@ import (
|
||||||
"github.com/coder/coder/v2/agent/agenttest"
|
"github.com/coder/coder/v2/agent/agenttest"
|
||||||
"github.com/coder/coder/v2/agent/proto"
|
"github.com/coder/coder/v2/agent/proto"
|
||||||
"github.com/coder/coder/v2/coderd"
|
"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/agentsdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/coder/v2/tailnet"
|
"github.com/coder/coder/v2/tailnet"
|
||||||
"github.com/coder/coder/v2/tailnet/tailnettest"
|
"github.com/coder/coder/v2/tailnet/tailnettest"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
|
@ -80,7 +80,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
|
||||||
agents, serverTailnet := setupServerTailnetAgent(t, 1)
|
agents, serverTailnet := setupServerTailnetAgent(t, 1)
|
||||||
a := agents[0]
|
a := agents[0]
|
||||||
|
|
||||||
u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", codersdk.WorkspaceAgentHTTPAPIServerPort))
|
u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rp := serverTailnet.ReverseProxy(u, u, a.id)
|
rp := serverTailnet.ReverseProxy(u, u, a.id)
|
||||||
|
@ -111,7 +111,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
require.NoError(t, registry.Register(serverTailnet))
|
require.NoError(t, registry.Register(serverTailnet))
|
||||||
|
|
||||||
u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", codersdk.WorkspaceAgentHTTPAPIServerPort))
|
u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rp := serverTailnet.ReverseProxy(u, u, a.id)
|
rp := serverTailnet.ReverseProxy(u, u, a.id)
|
||||||
|
@ -145,7 +145,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
|
||||||
agents, serverTailnet := setupServerTailnetAgent(t, 1)
|
agents, serverTailnet := setupServerTailnetAgent(t, 1)
|
||||||
a := agents[0]
|
a := agents[0]
|
||||||
|
|
||||||
u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", codersdk.WorkspaceAgentHTTPAPIServerPort))
|
u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rp := serverTailnet.ReverseProxy(u, u, a.id)
|
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.
|
// Ensure the reverse proxy director rewrites the url host to the agent's IP.
|
||||||
rp.Director(req)
|
rp.Director(req)
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
fmt.Sprintf("[%s]:%d", tailnet.IPFromUUID(a.id).String(), codersdk.WorkspaceAgentHTTPAPIServerPort),
|
fmt.Sprintf("[%s]:%d", tailnet.IPFromUUID(a.id).String(), workspacesdk.AgentHTTPAPIServerPort),
|
||||||
req.URL.Host,
|
req.URL.Host,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -315,7 +315,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
|
||||||
|
|
||||||
require.True(t, serverTailnet.Conn().GetBlockEndpoints(), "expected BlockEndpoints to be set")
|
require.True(t, serverTailnet.Conn().GetBlockEndpoints(), "expected BlockEndpoints to be set")
|
||||||
|
|
||||||
u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", codersdk.WorkspaceAgentHTTPAPIServerPort))
|
u, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", workspacesdk.AgentHTTPAPIServerPort))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rp := serverTailnet.ReverseProxy(u, u, a.id)
|
rp := serverTailnet.ReverseProxy(u, u, a.id)
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/schedule"
|
"github.com/coder/coder/v2/coderd/schedule"
|
||||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/provisioner/echo"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
)
|
)
|
||||||
|
@ -1235,9 +1236,10 @@ func TestTemplateMetrics(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Zero(t, res.Workspaces[0].LastUsedAt)
|
assert.Zero(t, res.Workspaces[0].LastUsedAt)
|
||||||
|
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: slogtest.Make(t, nil).Named("tailnet"),
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: slogtest.Make(t, nil).Named("tailnet"),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|
|
@ -39,6 +39,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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"
|
||||||
"github.com/coder/coder/v2/tailnet/proto"
|
"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.
|
// common non-HTTP ports such as databases, FTP, SSH, etc.
|
||||||
filteredPorts := make([]codersdk.WorkspaceAgentListeningPort, 0, len(portsResponse.Ports))
|
filteredPorts := make([]codersdk.WorkspaceAgentListeningPort, 0, len(portsResponse.Ports))
|
||||||
for _, port := range portsResponse.Ports {
|
for _, port := range portsResponse.Ports {
|
||||||
if port.Port < codersdk.WorkspaceAgentMinimumListeningPort {
|
if port.Port < workspacesdk.AgentMinimumListeningPort {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := appPorts[port.Port]; ok {
|
if _, ok := appPorts[port.Port]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := codersdk.WorkspaceAgentIgnoredListeningPorts[port.Port]; ok {
|
if _, ok := workspacesdk.AgentIgnoredListeningPorts[port.Port]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
filteredPorts = append(filteredPorts, port)
|
filteredPorts = append(filteredPorts, port)
|
||||||
|
@ -825,12 +826,12 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Tags Agents
|
// @Tags Agents
|
||||||
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
|
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
|
||||||
// @Success 200 {object} codersdk.WorkspaceAgentConnectionInfo
|
// @Success 200 {object} workspacesdk.AgentConnectionInfo
|
||||||
// @Router /workspaceagents/{workspaceagent}/connection [get]
|
// @Router /workspaceagents/{workspaceagent}/connection [get]
|
||||||
func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{
|
httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.AgentConnectionInfo{
|
||||||
DERPMap: api.DERPMap(),
|
DERPMap: api.DERPMap(),
|
||||||
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
||||||
DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
|
DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
|
||||||
|
@ -845,13 +846,13 @@ func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Tags Agents
|
// @Tags Agents
|
||||||
// @Success 200 {object} codersdk.WorkspaceAgentConnectionInfo
|
// @Success 200 {object} workspacesdk.AgentConnectionInfo
|
||||||
// @Router /workspaceagents/connection [get]
|
// @Router /workspaceagents/connection [get]
|
||||||
// @x-apidocgen {"skip": true}
|
// @x-apidocgen {"skip": true}
|
||||||
func (api *API) workspaceAgentConnectionGeneric(rw http.ResponseWriter, r *http.Request) {
|
func (api *API) workspaceAgentConnectionGeneric(rw http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{
|
httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.AgentConnectionInfo{
|
||||||
DERPMap: api.DERPMap(),
|
DERPMap: api.DERPMap(),
|
||||||
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
||||||
DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
|
DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
|
||||||
|
|
|
@ -37,6 +37,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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/provisioner/echo"
|
||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||||
"github.com/coder/coder/v2/tailnet/tailnettest"
|
"github.com/coder/coder/v2/tailnet/tailnettest"
|
||||||
|
@ -337,7 +338,8 @@ func TestWorkspaceAgentConnectRPC(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
|
conn, err := workspacesdk.New(client).
|
||||||
|
DialAgent(ctx, resources[0].Agents[0].ID, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
@ -448,13 +450,14 @@ func TestWorkspaceAgentTailnet(t *testing.T) {
|
||||||
_ = agenttest.New(t, client.URL, r.AgentToken)
|
_ = agenttest.New(t, client.URL, r.AgentToken)
|
||||||
resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
|
resources := coderdtest.AwaitWorkspaceAgents(t, client, r.Workspace.ID)
|
||||||
|
|
||||||
conn, err := func() (*codersdk.WorkspaceAgentConn, error) {
|
conn, err := func() (*workspacesdk.AgentConn, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
defer cancel() // Connection should remain open even if the dial context is canceled.
|
defer cancel() // Connection should remain open even if the dial context is canceled.
|
||||||
|
|
||||||
return client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, &codersdk.DialWorkspaceAgentOptions{
|
return workspacesdk.New(client).
|
||||||
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
||||||
|
})
|
||||||
}()
|
}()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
@ -547,7 +550,7 @@ func TestWorkspaceAgentTailnetDirectDisabled(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
var connInfo codersdk.WorkspaceAgentConnectionInfo
|
var connInfo workspacesdk.AgentConnectionInfo
|
||||||
err = json.NewDecoder(res.Body).Decode(&connInfo)
|
err = json.NewDecoder(res.Body).Decode(&connInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, connInfo.DisableDirectConnections)
|
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{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
DialAgent(ctx, resources[0].Agents[0].ID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
@ -603,10 +607,10 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
willFilterPort := func(port int) bool {
|
willFilterPort := func(port int) bool {
|
||||||
if port < codersdk.WorkspaceAgentMinimumListeningPort || port > 65535 {
|
if port < workspacesdk.AgentMinimumListeningPort || port > 65535 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := codersdk.WorkspaceAgentIgnoredListeningPorts[uint16(port)]; ok {
|
if _, ok := workspacesdk.AgentIgnoredListeningPorts[uint16(port)]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -646,7 +650,7 @@ func TestWorkspaceAgentListeningPorts(t *testing.T) {
|
||||||
port uint16
|
port uint16
|
||||||
)
|
)
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
for ignoredPort := range codersdk.WorkspaceAgentIgnoredListeningPorts {
|
for ignoredPort := range workspacesdk.AgentIgnoredListeningPorts {
|
||||||
if ignoredPort < 1024 || ignoredPort == 5432 {
|
if ignoredPort < 1024 || ignoredPort == 5432 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1623,13 +1627,14 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) {
|
||||||
agentID := resources[0].Agents[0].ID
|
agentID := resources[0].Agents[0].ID
|
||||||
|
|
||||||
// Connect from a client.
|
// Connect from a client.
|
||||||
conn1, err := func() (*codersdk.WorkspaceAgentConn, error) {
|
conn1, err := func() (*workspacesdk.AgentConn, error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
defer cancel() // Connection should remain open even if the dial context is canceled.
|
defer cancel() // Connection should remain open even if the dial context is canceled.
|
||||||
|
|
||||||
return client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
|
return workspacesdk.New(client).
|
||||||
Logger: logger.Named("client1"),
|
DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: logger.Named("client1"),
|
||||||
|
})
|
||||||
}()
|
}()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn1.Close()
|
defer conn1.Close()
|
||||||
|
@ -1672,9 +1677,10 @@ func TestWorkspaceAgent_UpdatedDERP(t *testing.T) {
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
// Connect from a second client.
|
// Connect from a second client.
|
||||||
conn2, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
|
conn2, err := workspacesdk.New(client).
|
||||||
Logger: logger.Named("client2"),
|
DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{
|
||||||
})
|
Logger: logger.Named("client2"),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn2.Close()
|
defer conn2.Close()
|
||||||
ok = conn2.AwaitReachable(ctx)
|
ok = conn2.AwaitReachable(ctx)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/coderd/workspaceapps"
|
"github.com/coder/coder/v2/coderd/workspaceapps"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -950,7 +951,7 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
app := appDetails.Apps.Port
|
app := appDetails.Apps.Port
|
||||||
app.AppSlugOrPort = strconv.Itoa(codersdk.WorkspaceAgentMinimumListeningPort - 1)
|
app.AppSlugOrPort = strconv.Itoa(workspacesdk.AgentMinimumListeningPort - 1)
|
||||||
resp, err := requestWithRetries(ctx, t, appDetails.AppClient(t), http.MethodGet, appDetails.SubdomainAppURL(app).String(), nil)
|
resp, err := requestWithRetries(ctx, t, appDetails.AppClient(t), http.MethodGet, appDetails.SubdomainAppURL(app).String(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
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) {
|
func testReconnectingPTY(ctx context.Context, t *testing.T, client *codersdk.Client, agentID uuid.UUID, signedToken string) {
|
||||||
opts := codersdk.WorkspaceAgentReconnectingPTYOpts{
|
opts := workspacesdk.WorkspaceAgentReconnectingPTYOpts{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Reconnect: uuid.New(),
|
Reconnect: uuid.New(),
|
||||||
Width: 80,
|
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")
|
return strings.Contains(line, "exit") || strings.Contains(line, "logout")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := client.WorkspaceAgentReconnectingPTY(ctx, opts)
|
conn, err := workspacesdk.New(client).AgentReconnectingPTY(ctx, opts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
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
|
// 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")
|
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",
|
Data: "echo test\r",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
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")
|
require.NoError(t, tr.ReadUntil(ctx, matchEchoOutput), "find echo output")
|
||||||
|
|
||||||
// Exit should cause the connection to close.
|
// Exit should cause the connection to close.
|
||||||
data, err = json.Marshal(codersdk.ReconnectingPTYRequest{
|
data, err = json.Marshal(workspacesdk.ReconnectingPTYRequest{
|
||||||
Data: "exit\r",
|
Data: "exit\r",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/util/slice"
|
"github.com/coder/coder/v2/coderd/util/slice"
|
||||||
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
|
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/coder/v2/site"
|
"github.com/coder/coder/v2/site"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ type AgentProvider interface {
|
||||||
ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID) *httputil.ReverseProxy
|
ReverseProxy(targetURL, dashboardURL *url.URL, agentID uuid.UUID) *httputil.ReverseProxy
|
||||||
|
|
||||||
// AgentConn returns a new connection to the specified agent.
|
// AgentConn returns a new connection to the specified agent.
|
||||||
AgentConn(ctx context.Context, agentID uuid.UUID) (_ *codersdk.WorkspaceAgentConn, release func(), _ error)
|
AgentConn(ctx context.Context, agentID uuid.UUID) (_ *workspacesdk.AgentConn, release func(), _ error)
|
||||||
|
|
||||||
ServeHTTPDebug(w http.ResponseWriter, r *http.Request)
|
ServeHTTPDebug(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
|
@ -513,9 +514,11 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if portInt < codersdk.WorkspaceAgentMinimumListeningPort {
|
if portInt < workspacesdk.AgentMinimumListeningPort {
|
||||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
Message: fmt.Sprintf("Application port %d is not permitted. Coder reserves ports less than %d for internal use.", portInt, codersdk.WorkspaceAgentMinimumListeningPort),
|
Message: fmt.Sprintf("Application port %d is not permitted. Coder reserves ports less than %d for internal use.",
|
||||||
|
portInt, workspacesdk.AgentMinimumListeningPort,
|
||||||
|
),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package codersdk
|
package healthsdk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -6,15 +6,24 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors"
|
||||||
"tailscale.com/derp"
|
"tailscale.com/derp"
|
||||||
"tailscale.com/net/netcheck"
|
"tailscale.com/net/netcheck"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
"github.com/coder/coder/v2/coderd/healthcheck/health"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"golang.org/x/xerrors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// @typescript-ignore HealthClient
|
||||||
|
type HealthClient struct {
|
||||||
|
client *codersdk.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(c *codersdk.Client) *HealthClient {
|
||||||
|
return &HealthClient{client: c}
|
||||||
|
}
|
||||||
|
|
||||||
type HealthSection string
|
type HealthSection string
|
||||||
|
|
||||||
// If you add another const below, make sure to add it to HealthSections!
|
// If you add another const below, make sure to add it to HealthSections!
|
||||||
|
@ -44,34 +53,34 @@ type UpdateHealthSettings struct {
|
||||||
DismissedHealthchecks []HealthSection `json:"dismissed_healthchecks"`
|
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.Request(ctx, http.MethodGet, "/api/v2/debug/health", nil)
|
res, err := c.client.Request(ctx, http.MethodGet, "/api/v2/debug/health", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return HealthcheckReport{}, err
|
return HealthcheckReport{}, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return HealthcheckReport{}, ReadBodyAsError(res)
|
return HealthcheckReport{}, codersdk.ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
var rpt HealthcheckReport
|
var rpt HealthcheckReport
|
||||||
return rpt, json.NewDecoder(res.Body).Decode(&rpt)
|
return rpt, json.NewDecoder(res.Body).Decode(&rpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) HealthSettings(ctx context.Context) (HealthSettings, error) {
|
func (c *HealthClient) HealthSettings(ctx context.Context) (HealthSettings, error) {
|
||||||
res, err := c.Request(ctx, http.MethodGet, "/api/v2/debug/health/settings", nil)
|
res, err := c.client.Request(ctx, http.MethodGet, "/api/v2/debug/health/settings", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return HealthSettings{}, err
|
return HealthSettings{}, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return HealthSettings{}, ReadBodyAsError(res)
|
return HealthSettings{}, codersdk.ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
var settings HealthSettings
|
var settings HealthSettings
|
||||||
return settings, json.NewDecoder(res.Body).Decode(&settings)
|
return settings, json.NewDecoder(res.Body).Decode(&settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PutHealthSettings(ctx context.Context, settings HealthSettings) error {
|
func (c *HealthClient) PutHealthSettings(ctx context.Context, settings HealthSettings) error {
|
||||||
res, err := c.Request(ctx, http.MethodPut, "/api/v2/debug/health/settings", settings)
|
res, err := c.client.Request(ctx, http.MethodPut, "/api/v2/debug/health/settings", settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -81,7 +90,7 @@ func (c *Client) PutHealthSettings(ctx context.Context, settings HealthSettings)
|
||||||
return xerrors.New("health settings not modified")
|
return xerrors.New("health settings not modified")
|
||||||
}
|
}
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return ReadBodyAsError(res)
|
return codersdk.ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -199,8 +208,8 @@ type ProvisionerDaemonsReport struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProvisionerDaemonsReportItem struct {
|
type ProvisionerDaemonsReportItem struct {
|
||||||
ProvisionerDaemon `json:"provisioner_daemon"`
|
codersdk.ProvisionerDaemon `json:"provisioner_daemon"`
|
||||||
Warnings []health.Message `json:"warnings"`
|
Warnings []health.Message `json:"warnings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsocketReport struct {
|
type WebsocketReport struct {
|
||||||
|
@ -222,5 +231,5 @@ type WorkspaceProxyReport struct {
|
||||||
Dismissed bool `json:"dismissed"`
|
Dismissed bool `json:"dismissed"`
|
||||||
Error *string `json:"error"`
|
Error *string `json:"error"`
|
||||||
|
|
||||||
WorkspaceProxies RegionsResponse[WorkspaceProxy] `json:"workspace_proxies"`
|
WorkspaceProxies codersdk.RegionsResponse[codersdk.WorkspaceProxy] `json:"workspace_proxies"`
|
||||||
}
|
}
|
|
@ -3,28 +3,18 @@ package codersdk
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
"nhooyr.io/websocket"
|
"nhooyr.io/websocket"
|
||||||
"tailscale.com/tailcfg"
|
|
||||||
|
|
||||||
"cdr.dev/slog"
|
|
||||||
"github.com/coder/coder/v2/coderd/tracing"
|
"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
|
type WorkspaceAgentStatus string
|
||||||
|
@ -215,360 +205,28 @@ type DERPRegion struct {
|
||||||
LatencyMilliseconds float64 `json:"latency_ms"`
|
LatencyMilliseconds float64 `json:"latency_ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WorkspaceAgentConnectionInfo returns required information for establishing
|
type WorkspaceAgentLog struct {
|
||||||
// a connection with a workspace.
|
ID int64 `json:"id"`
|
||||||
// @typescript-ignore WorkspaceAgentConnectionInfo
|
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||||
type WorkspaceAgentConnectionInfo struct {
|
Output string `json:"output"`
|
||||||
DERPMap *tailcfg.DERPMap `json:"derp_map"`
|
Level LogLevel `json:"level"`
|
||||||
DERPForceWebSockets bool `json:"derp_force_websockets"`
|
SourceID uuid.UUID `json:"source_id" format:"uuid"`
|
||||||
DisableDirectConnections bool `json:"disable_direct_connections"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) WorkspaceAgentConnectionInfoGeneric(ctx context.Context) (WorkspaceAgentConnectionInfo, error) {
|
type AgentSubsystem string
|
||||||
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
|
const (
|
||||||
return connInfo, json.NewDecoder(res.Body).Decode(&connInfo)
|
AgentSubsystemEnvbox AgentSubsystem = "envbox"
|
||||||
}
|
AgentSubsystemEnvbuilder AgentSubsystem = "envbuilder"
|
||||||
|
AgentSubsystemExectrace AgentSubsystem = "exectrace"
|
||||||
|
)
|
||||||
|
|
||||||
func (c *Client) WorkspaceAgentConnectionInfo(ctx context.Context, agentID uuid.UUID) (WorkspaceAgentConnectionInfo, error) {
|
func (s AgentSubsystem) Valid() bool {
|
||||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaceagents/%s/connection", agentID), nil)
|
switch s {
|
||||||
if err != nil {
|
case AgentSubsystemEnvbox, AgentSubsystemEnvbuilder, AgentSubsystemExectrace:
|
||||||
return WorkspaceAgentConnectionInfo{}, err
|
return true
|
||||||
}
|
default:
|
||||||
defer res.Body.Close()
|
return false
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
return WorkspaceAgentConnectionInfo{}, ReadBodyAsError(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
var connInfo WorkspaceAgentConnectionInfo
|
|
||||||
return connInfo, json.NewDecoder(res.Body).Decode(&connInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @typescript-ignore DialWorkspaceAgentOptions
|
|
||||||
type DialWorkspaceAgentOptions struct {
|
|
||||||
Logger slog.Logger
|
|
||||||
// BlockEndpoints forced a direct connection through DERP. The Client may
|
|
||||||
// have DisableDirect set which will override this value.
|
|
||||||
BlockEndpoints bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) DialWorkspaceAgent(dialCtx context.Context, agentID uuid.UUID, options *DialWorkspaceAgentOptions) (agentConn *WorkspaceAgentConn, err error) {
|
|
||||||
if options == nil {
|
|
||||||
options = &DialWorkspaceAgentOptions{}
|
|
||||||
}
|
|
||||||
|
|
||||||
connInfo, err := c.WorkspaceAgentConnectionInfo(dialCtx, agentID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("get connection info: %w", err)
|
|
||||||
}
|
|
||||||
if connInfo.DisableDirectConnections {
|
|
||||||
options.BlockEndpoints = true
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := tailnet.IP()
|
|
||||||
var header http.Header
|
|
||||||
if headerTransport, ok := c.HTTPClient.Transport.(*HeaderTransport); ok {
|
|
||||||
header = headerTransport.Header
|
|
||||||
}
|
|
||||||
conn, err := tailnet.NewConn(&tailnet.Options{
|
|
||||||
Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)},
|
|
||||||
DERPMap: connInfo.DERPMap,
|
|
||||||
DERPHeader: &header,
|
|
||||||
DERPForceWebSockets: connInfo.DERPForceWebSockets,
|
|
||||||
Logger: options.Logger,
|
|
||||||
BlockEndpoints: c.DisableDirectConnections || options.BlockEndpoints,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("create tailnet: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
headers := make(http.Header)
|
|
||||||
tokenHeader := SessionTokenHeader
|
|
||||||
if c.SessionTokenHeader != "" {
|
|
||||||
tokenHeader = c.SessionTokenHeader
|
|
||||||
}
|
|
||||||
headers.Set(tokenHeader, c.SessionToken())
|
|
||||||
|
|
||||||
// New context, separate from dialCtx. We don't want to cancel the
|
|
||||||
// connection if dialCtx is canceled.
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
coordinateURL, err := c.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/coordinate", agentID))
|
|
||||||
if err != nil {
|
|
||||||
return nil, xerrors.Errorf("parse url: %w", err)
|
|
||||||
}
|
|
||||||
q := coordinateURL.Query()
|
|
||||||
q.Add("version", proto.CurrentVersion.String())
|
|
||||||
coordinateURL.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
connector := runTailnetAPIConnector(ctx, options.Logger,
|
|
||||||
agentID, coordinateURL.String(),
|
|
||||||
&websocket.DialOptions{
|
|
||||||
HTTPClient: c.HTTPClient,
|
|
||||||
HTTPHeader: headers,
|
|
||||||
// Need to disable compression to avoid a data-race.
|
|
||||||
CompressionMode: websocket.CompressionDisabled,
|
|
||||||
},
|
|
||||||
conn,
|
|
||||||
)
|
|
||||||
options.Logger.Debug(ctx, "running tailnet API v2+ connector")
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-dialCtx.Done():
|
|
||||||
return nil, xerrors.Errorf("timed out waiting for coordinator and derp map: %w", dialCtx.Err())
|
|
||||||
case err = <-connector.connected:
|
|
||||||
if err != nil {
|
|
||||||
options.Logger.Error(ctx, "failed to connect to tailnet v2+ API", slog.Error(err))
|
|
||||||
return nil, xerrors.Errorf("start connector: %w", err)
|
|
||||||
}
|
|
||||||
options.Logger.Debug(ctx, "connected to tailnet v2+ API")
|
|
||||||
}
|
|
||||||
|
|
||||||
agentConn = NewWorkspaceAgentConn(conn, WorkspaceAgentConnOptions{
|
|
||||||
AgentID: agentID,
|
|
||||||
CloseFunc: func() error {
|
|
||||||
cancel()
|
|
||||||
<-connector.closed
|
|
||||||
return conn.Close()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if !agentConn.AwaitReachable(dialCtx) {
|
|
||||||
_ = agentConn.Close()
|
|
||||||
return nil, xerrors.Errorf("timed out waiting for agent to become reachable: %w", dialCtx.Err())
|
|
||||||
}
|
|
||||||
|
|
||||||
return agentConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// tailnetConn is the subset of the tailnet.Conn methods that tailnetAPIConnector uses. It is
|
|
||||||
// included so that we can fake it in testing.
|
|
||||||
//
|
|
||||||
// @typescript-ignore tailnetConn
|
|
||||||
type tailnetConn interface {
|
|
||||||
tailnet.Coordinatee
|
|
||||||
SetDERPMap(derpMap *tailcfg.DERPMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tailnetAPIConnector dials the tailnet API (v2+) and then uses the API with a tailnet.Conn to
|
|
||||||
//
|
|
||||||
// 1) run the Coordinate API and pass node information back and forth
|
|
||||||
// 2) stream DERPMap updates and program the Conn
|
|
||||||
//
|
|
||||||
// These functions share the same websocket, and so are combined here so that if we hit a problem
|
|
||||||
// we tear the whole thing down and start over with a new websocket.
|
|
||||||
//
|
|
||||||
// @typescript-ignore tailnetAPIConnector
|
|
||||||
type tailnetAPIConnector struct {
|
|
||||||
// We keep track of two contexts: the main context from the caller, and a "graceful" context
|
|
||||||
// that we keep open slightly longer than the main context to give a chance to send the
|
|
||||||
// Disconnect message to the coordinator. That tells the coordinator that we really meant to
|
|
||||||
// disconnect instead of just losing network connectivity.
|
|
||||||
ctx context.Context
|
|
||||||
gracefulCtx context.Context
|
|
||||||
cancelGracefulCtx context.CancelFunc
|
|
||||||
|
|
||||||
logger slog.Logger
|
|
||||||
|
|
||||||
agentID uuid.UUID
|
|
||||||
coordinateURL string
|
|
||||||
dialOptions *websocket.DialOptions
|
|
||||||
conn tailnetConn
|
|
||||||
|
|
||||||
connected chan error
|
|
||||||
isFirst bool
|
|
||||||
closed chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// runTailnetAPIConnector creates and runs a tailnetAPIConnector
|
|
||||||
func runTailnetAPIConnector(
|
|
||||||
ctx context.Context, logger slog.Logger,
|
|
||||||
agentID uuid.UUID, coordinateURL string, dialOptions *websocket.DialOptions,
|
|
||||||
conn tailnetConn,
|
|
||||||
) *tailnetAPIConnector {
|
|
||||||
tac := &tailnetAPIConnector{
|
|
||||||
ctx: ctx,
|
|
||||||
logger: logger,
|
|
||||||
agentID: agentID,
|
|
||||||
coordinateURL: coordinateURL,
|
|
||||||
dialOptions: dialOptions,
|
|
||||||
conn: conn,
|
|
||||||
connected: make(chan error, 1),
|
|
||||||
closed: make(chan struct{}),
|
|
||||||
}
|
|
||||||
tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background())
|
|
||||||
go tac.manageGracefulTimeout()
|
|
||||||
go tac.run()
|
|
||||||
return tac
|
|
||||||
}
|
|
||||||
|
|
||||||
// manageGracefulTimeout allows the gracefulContext to last 1 second longer than the main context
|
|
||||||
// to allow a graceful disconnect.
|
|
||||||
func (tac *tailnetAPIConnector) manageGracefulTimeout() {
|
|
||||||
defer tac.cancelGracefulCtx()
|
|
||||||
<-tac.ctx.Done()
|
|
||||||
select {
|
|
||||||
case <-tac.closed:
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tac *tailnetAPIConnector) run() {
|
|
||||||
tac.isFirst = true
|
|
||||||
defer close(tac.closed)
|
|
||||||
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); {
|
|
||||||
tailnetClient, err := tac.dial()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tac.logger.Debug(tac.ctx, "obtained tailnet API v2+ client")
|
|
||||||
tac.coordinateAndDERPMap(tailnetClient)
|
|
||||||
tac.logger.Debug(tac.ctx, "tailnet API v2+ connection lost")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) {
|
|
||||||
tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API")
|
|
||||||
// nolint:bodyclose
|
|
||||||
ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions)
|
|
||||||
if tac.isFirst {
|
|
||||||
if res != nil && res.StatusCode == http.StatusConflict {
|
|
||||||
err = ReadBodyAsError(res)
|
|
||||||
tac.connected <- err
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tac.isFirst = false
|
|
||||||
close(tac.connected)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, context.Canceled) {
|
|
||||||
tac.logger.Error(tac.ctx, "failed to dial tailnet v2+ API", slog.Error(err))
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client, err := tailnet.NewDRPCClient(
|
|
||||||
websocket.NetConn(tac.gracefulCtx, ws, websocket.MessageBinary),
|
|
||||||
tac.logger,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
tac.logger.Debug(tac.ctx, "failed to create DRPCClient", slog.Error(err))
|
|
||||||
_ = ws.Close(websocket.StatusInternalError, "")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return client, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// coordinateAndDERPMap uses the provided client to coordinate and stream DERP Maps. It is combined
|
|
||||||
// into one function so that a problem with one tears down the other and triggers a retry (if
|
|
||||||
// appropriate). We multiplex both RPCs over the same websocket, so we want them to share the same
|
|
||||||
// fate.
|
|
||||||
func (tac *tailnetAPIConnector) coordinateAndDERPMap(client proto.DRPCTailnetClient) {
|
|
||||||
defer func() {
|
|
||||||
conn := client.DRPCConn()
|
|
||||||
closeErr := conn.Close()
|
|
||||||
if closeErr != nil &&
|
|
||||||
!xerrors.Is(closeErr, io.EOF) &&
|
|
||||||
!xerrors.Is(closeErr, context.Canceled) &&
|
|
||||||
!xerrors.Is(closeErr, context.DeadlineExceeded) {
|
|
||||||
tac.logger.Error(tac.ctx, "error closing DRPC connection", slog.Error(closeErr))
|
|
||||||
<-conn.Closed()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(2)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
tac.coordinate(client)
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
dErr := tac.derpMap(client)
|
|
||||||
if dErr != nil && tac.ctx.Err() == nil {
|
|
||||||
// The main context is still active, meaning that we want the tailnet data plane to stay
|
|
||||||
// up, even though we hit some error getting DERP maps on the control plane. That means
|
|
||||||
// we do NOT want to gracefully disconnect on the coordinate() routine. So, we'll just
|
|
||||||
// close the underlying connection. This will trigger a retry of the control plane in
|
|
||||||
// run().
|
|
||||||
client.DRPCConn().Close()
|
|
||||||
// Note that derpMap() logs it own errors, we don't bother here.
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tac *tailnetAPIConnector) coordinate(client proto.DRPCTailnetClient) {
|
|
||||||
// we use the gracefulCtx here so that we'll have time to send the graceful disconnect
|
|
||||||
coord, err := client.Coordinate(tac.gracefulCtx)
|
|
||||||
if err != nil {
|
|
||||||
tac.logger.Error(tac.ctx, "failed to connect to Coordinate RPC", slog.Error(err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
cErr := coord.Close()
|
|
||||||
if cErr != nil {
|
|
||||||
tac.logger.Debug(tac.ctx, "error closing Coordinate RPC", slog.Error(cErr))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
coordination := tailnet.NewRemoteCoordination(tac.logger, coord, tac.conn, tac.agentID)
|
|
||||||
tac.logger.Debug(tac.ctx, "serving coordinator")
|
|
||||||
select {
|
|
||||||
case <-tac.ctx.Done():
|
|
||||||
tac.logger.Debug(tac.ctx, "main context canceled; do graceful disconnect")
|
|
||||||
crdErr := coordination.Close()
|
|
||||||
if crdErr != nil {
|
|
||||||
tac.logger.Warn(tac.ctx, "failed to close remote coordination", slog.Error(err))
|
|
||||||
}
|
|
||||||
case err = <-coordination.Error():
|
|
||||||
if err != nil &&
|
|
||||||
!xerrors.Is(err, io.EOF) &&
|
|
||||||
!xerrors.Is(err, context.Canceled) &&
|
|
||||||
!xerrors.Is(err, context.DeadlineExceeded) {
|
|
||||||
tac.logger.Error(tac.ctx, "remote coordination error", slog.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tac *tailnetAPIConnector) derpMap(client proto.DRPCTailnetClient) error {
|
|
||||||
s, err := client.StreamDERPMaps(tac.ctx, &proto.StreamDERPMapsRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("failed to connect to StreamDERPMaps RPC: %w", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
cErr := s.Close()
|
|
||||||
if cErr != nil {
|
|
||||||
tac.logger.Debug(tac.ctx, "error closing StreamDERPMaps RPC", slog.Error(cErr))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
dmp, err := s.Recv()
|
|
||||||
if err != nil {
|
|
||||||
if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
tac.logger.Error(tac.ctx, "error receiving DERP Map", slog.Error(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tac.logger.Debug(tac.ctx, "got new DERP Map", slog.F("derp_map", dmp))
|
|
||||||
dm := tailnet.DERPMapFromProto(dmp)
|
|
||||||
tac.conn.SetDERPMap(dm)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -702,66 +360,18 @@ func (c *Client) IssueReconnectingPTYSignedToken(ctx context.Context, req IssueR
|
||||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @typescript-ignore:WorkspaceAgentReconnectingPTYOpts
|
type WorkspaceAgentListeningPortsResponse struct {
|
||||||
type WorkspaceAgentReconnectingPTYOpts struct {
|
// If there are no ports in the list, nothing should be displayed in the UI.
|
||||||
AgentID uuid.UUID
|
// There must not be a "no ports available" message or anything similar, as
|
||||||
Reconnect uuid.UUID
|
// there will always be no ports displayed on platforms where our port
|
||||||
Width uint16
|
// detection logic is unsupported.
|
||||||
Height uint16
|
Ports []WorkspaceAgentListeningPort `json:"ports"`
|
||||||
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.
|
type WorkspaceAgentListeningPort struct {
|
||||||
// It communicates using `agent.ReconnectingPTYRequest` marshaled as JSON.
|
ProcessName string `json:"process_name"` // may be empty
|
||||||
// Responses are PTY output that can be rendered.
|
Network string `json:"network"` // only "tcp" at the moment
|
||||||
func (c *Client) WorkspaceAgentReconnectingPTY(ctx context.Context, opts WorkspaceAgentReconnectingPTYOpts) (net.Conn, error) {
|
Port uint16 `json:"port"`
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WorkspaceAgentListeningPorts returns a list of ports that are currently being
|
// 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
|
return nil
|
||||||
}), 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package codersdk
|
package workspacesdk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -9,9 +9,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -23,141 +21,40 @@ import (
|
||||||
"tailscale.com/net/speedtest"
|
"tailscale.com/net/speedtest"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/tracing"
|
"github.com/coder/coder/v2/coderd/tracing"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/tailnet"
|
"github.com/coder/coder/v2/tailnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WorkspaceAgentIP is a static IPv6 address with the Tailscale prefix that is used to route
|
// NewAgentConn creates a new WorkspaceAgentConn. `conn` may be unique
|
||||||
// connections from clients to this node. A dynamic address is not required because a Tailnet
|
|
||||||
// client only dials a single agent at a time.
|
|
||||||
//
|
|
||||||
// Deprecated: use tailnet.IP() instead. This is kept for backwards
|
|
||||||
// compatibility with outdated CLI clients and Workspace Proxies that dial it.
|
|
||||||
// See: https://github.com/coder/coder/issues/11819
|
|
||||||
var WorkspaceAgentIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4")
|
|
||||||
|
|
||||||
var ErrSkipClose = xerrors.New("skip tailnet close")
|
|
||||||
|
|
||||||
const (
|
|
||||||
WorkspaceAgentSSHPort = tailnet.WorkspaceAgentSSHPort
|
|
||||||
WorkspaceAgentReconnectingPTYPort = tailnet.WorkspaceAgentReconnectingPTYPort
|
|
||||||
WorkspaceAgentSpeedtestPort = tailnet.WorkspaceAgentSpeedtestPort
|
|
||||||
// WorkspaceAgentHTTPAPIServerPort serves a HTTP server with endpoints for e.g.
|
|
||||||
// gathering agent statistics.
|
|
||||||
WorkspaceAgentHTTPAPIServerPort = 4
|
|
||||||
|
|
||||||
// WorkspaceAgentMinimumListeningPort is the minimum port that the listening-ports
|
|
||||||
// endpoint will return to the client, and the minimum port that is accepted
|
|
||||||
// by the proxy applications endpoint. Coder consumes ports 1-4 at the
|
|
||||||
// moment, and we reserve some extra ports for future use. Port 9 and up are
|
|
||||||
// available for the user.
|
|
||||||
//
|
|
||||||
// This is not enforced in the CLI intentionally as we don't really care
|
|
||||||
// *that* much. The user could bypass this in the CLI by using SSH instead
|
|
||||||
// anyways.
|
|
||||||
WorkspaceAgentMinimumListeningPort = 9
|
|
||||||
)
|
|
||||||
|
|
||||||
// WorkspaceAgentIgnoredListeningPorts contains a list of ports to ignore when looking for
|
|
||||||
// running applications inside a workspace. We want to ignore non-HTTP servers,
|
|
||||||
// so we pre-populate this list with common ports that are not HTTP servers.
|
|
||||||
//
|
|
||||||
// This is implemented as a map for fast lookup.
|
|
||||||
var WorkspaceAgentIgnoredListeningPorts = map[uint16]struct{}{
|
|
||||||
0: {},
|
|
||||||
// Ports 1-8 are reserved for future use by the Coder agent.
|
|
||||||
1: {},
|
|
||||||
2: {},
|
|
||||||
3: {},
|
|
||||||
4: {},
|
|
||||||
5: {},
|
|
||||||
6: {},
|
|
||||||
7: {},
|
|
||||||
8: {},
|
|
||||||
// ftp
|
|
||||||
20: {},
|
|
||||||
21: {},
|
|
||||||
// ssh
|
|
||||||
22: {},
|
|
||||||
// telnet
|
|
||||||
23: {},
|
|
||||||
// smtp
|
|
||||||
25: {},
|
|
||||||
// dns over TCP
|
|
||||||
53: {},
|
|
||||||
// pop3
|
|
||||||
110: {},
|
|
||||||
// imap
|
|
||||||
143: {},
|
|
||||||
// bgp
|
|
||||||
179: {},
|
|
||||||
// ldap
|
|
||||||
389: {},
|
|
||||||
636: {},
|
|
||||||
// smtps
|
|
||||||
465: {},
|
|
||||||
// smtp
|
|
||||||
587: {},
|
|
||||||
// ftps
|
|
||||||
989: {},
|
|
||||||
990: {},
|
|
||||||
// imaps
|
|
||||||
993: {},
|
|
||||||
// pop3s
|
|
||||||
995: {},
|
|
||||||
// mysql
|
|
||||||
3306: {},
|
|
||||||
// rdp
|
|
||||||
3389: {},
|
|
||||||
// postgres
|
|
||||||
5432: {},
|
|
||||||
// mongodb
|
|
||||||
27017: {},
|
|
||||||
27018: {},
|
|
||||||
27019: {},
|
|
||||||
28017: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if !strings.HasSuffix(os.Args[0], ".test") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Add a thousand more ports to the ignore list during tests so it's easier
|
|
||||||
// to find an available port.
|
|
||||||
for i := 63000; i < 64000; i++ {
|
|
||||||
WorkspaceAgentIgnoredListeningPorts[uint16(i)] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWorkspaceAgentConn creates a new WorkspaceAgentConn. `conn` may be unique
|
|
||||||
// to the WorkspaceAgentConn, or it may be shared in the case of coderd. If the
|
// 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
|
// conn is shared and closing it is undesirable, you may return ErrNoClose from
|
||||||
// opts.CloseFunc. This will ensure the underlying conn is not closed.
|
// opts.CloseFunc. This will ensure the underlying conn is not closed.
|
||||||
func NewWorkspaceAgentConn(conn *tailnet.Conn, opts WorkspaceAgentConnOptions) *WorkspaceAgentConn {
|
func NewAgentConn(conn *tailnet.Conn, opts AgentConnOptions) *AgentConn {
|
||||||
return &WorkspaceAgentConn{
|
return &AgentConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
opts: opts,
|
opts: opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WorkspaceAgentConn represents a connection to a workspace agent.
|
// AgentConn represents a connection to a workspace agent.
|
||||||
// @typescript-ignore WorkspaceAgentConn
|
// @typescript-ignore AgentConn
|
||||||
type WorkspaceAgentConn struct {
|
type AgentConn struct {
|
||||||
*tailnet.Conn
|
*tailnet.Conn
|
||||||
opts WorkspaceAgentConnOptions
|
opts AgentConnOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// @typescript-ignore WorkspaceAgentConnOptions
|
// @typescript-ignore AgentConnOptions
|
||||||
type WorkspaceAgentConnOptions struct {
|
type AgentConnOptions struct {
|
||||||
AgentID uuid.UUID
|
AgentID uuid.UUID
|
||||||
CloseFunc func() error
|
CloseFunc func() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WorkspaceAgentConn) agentAddress() netip.Addr {
|
func (c *AgentConn) agentAddress() netip.Addr {
|
||||||
return tailnet.IPFromUUID(c.opts.AgentID)
|
return tailnet.IPFromUUID(c.opts.AgentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AwaitReachable waits for the agent to be reachable.
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -166,7 +63,7 @@ func (c *WorkspaceAgentConn) AwaitReachable(ctx context.Context) bool {
|
||||||
|
|
||||||
// Ping pings the agent and returns the round-trip time.
|
// Ping pings the agent and returns the round-trip time.
|
||||||
// The bool returns true if the ping was made P2P.
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -174,7 +71,7 @@ func (c *WorkspaceAgentConn) Ping(ctx context.Context) (time.Duration, bool, *ip
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close ends the connection to the workspace agent.
|
// Close ends the connection to the workspace agent.
|
||||||
func (c *WorkspaceAgentConn) Close() error {
|
func (c *AgentConn) Close() error {
|
||||||
var cerr error
|
var cerr error
|
||||||
if c.opts.CloseFunc != nil {
|
if c.opts.CloseFunc != nil {
|
||||||
cerr = c.opts.CloseFunc()
|
cerr = c.opts.CloseFunc()
|
||||||
|
@ -188,9 +85,9 @@ func (c *WorkspaceAgentConn) Close() error {
|
||||||
return c.Conn.Close()
|
return c.Conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WorkspaceAgentReconnectingPTYInit initializes a new reconnecting PTY session.
|
// AgentReconnectingPTYInit initializes a new reconnecting PTY session.
|
||||||
// @typescript-ignore WorkspaceAgentReconnectingPTYInit
|
// @typescript-ignore AgentReconnectingPTYInit
|
||||||
type WorkspaceAgentReconnectingPTYInit struct {
|
type AgentReconnectingPTYInit struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
Height uint16
|
Height uint16
|
||||||
Width uint16
|
Width uint16
|
||||||
|
@ -209,7 +106,7 @@ type ReconnectingPTYRequest struct {
|
||||||
// ReconnectingPTY spawns a new reconnecting terminal session.
|
// ReconnectingPTY spawns a new reconnecting terminal session.
|
||||||
// `ReconnectingPTYRequest` should be JSON marshaled and written to the returned net.Conn.
|
// `ReconnectingPTYRequest` should be JSON marshaled and written to the returned net.Conn.
|
||||||
// Raw terminal output will be read from 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -217,11 +114,11 @@ func (c *WorkspaceAgentConn) ReconnectingPTY(ctx context.Context, id uuid.UUID,
|
||||||
return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err())
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(WorkspaceAgentReconnectingPTYInit{
|
data, err := json.Marshal(AgentReconnectingPTYInit{
|
||||||
ID: id,
|
ID: id,
|
||||||
Height: height,
|
Height: height,
|
||||||
Width: width,
|
Width: width,
|
||||||
|
@ -244,7 +141,7 @@ func (c *WorkspaceAgentConn) ReconnectingPTY(ctx context.Context, id uuid.UUID,
|
||||||
|
|
||||||
// SSH pipes the SSH protocol over the returned net.Conn.
|
// SSH pipes the SSH protocol over the returned net.Conn.
|
||||||
// This connects to the built-in SSH server in the workspace agent.
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -252,12 +149,12 @@ func (c *WorkspaceAgentConn) SSH(ctx context.Context) (*gonet.TCPConn, error) {
|
||||||
return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err())
|
return 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
|
// SSHClient calls SSH to create a client that uses a weak cipher
|
||||||
// to improve throughput.
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -280,7 +177,7 @@ func (c *WorkspaceAgentConn) SSHClient(ctx context.Context) (*ssh.Client, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speedtest runs a speedtest against the workspace agent.
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -288,7 +185,7 @@ func (c *WorkspaceAgentConn) Speedtest(ctx context.Context, direction speedtest.
|
||||||
return nil, xerrors.Errorf("workspace agent not reachable in time: %v", ctx.Err())
|
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 {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("dial speedtest: %w", err)
|
return nil, xerrors.Errorf("dial speedtest: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -303,7 +200,7 @@ func (c *WorkspaceAgentConn) Speedtest(ctx context.Context, direction speedtest.
|
||||||
|
|
||||||
// DialContext dials the address provided in the workspace agent.
|
// DialContext dials the address provided in the workspace agent.
|
||||||
// The network must be "tcp" or "udp".
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -325,39 +222,25 @@ func (c *WorkspaceAgentConn) DialContext(ctx context.Context, network string, ad
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkspaceAgentListeningPortsResponse struct {
|
|
||||||
// If there are no ports in the list, nothing should be displayed in the UI.
|
|
||||||
// There must not be a "no ports available" message or anything similar, as
|
|
||||||
// there will always be no ports displayed on platforms where our port
|
|
||||||
// detection logic is unsupported.
|
|
||||||
Ports []WorkspaceAgentListeningPort `json:"ports"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WorkspaceAgentListeningPort struct {
|
|
||||||
ProcessName string `json:"process_name"` // may be empty
|
|
||||||
Network string `json:"network"` // only "tcp" at the moment
|
|
||||||
Port uint16 `json:"port"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListeningPorts lists the ports that are currently in use by the workspace.
|
// ListeningPorts lists the ports that are currently in use by the workspace.
|
||||||
func (c *WorkspaceAgentConn) ListeningPorts(ctx context.Context) (WorkspaceAgentListeningPortsResponse, error) {
|
func (c *AgentConn) ListeningPorts(ctx context.Context) (codersdk.WorkspaceAgentListeningPortsResponse, error) {
|
||||||
ctx, span := tracing.StartSpan(ctx)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
res, err := c.apiRequest(ctx, http.MethodGet, "/api/v0/listening-ports", nil)
|
res, err := c.apiRequest(ctx, http.MethodGet, "/api/v0/listening-ports", nil)
|
||||||
if err != 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()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
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)
|
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugMagicsock makes a request to the workspace agent's magicsock debug endpoint.
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
res, err := c.apiRequest(ctx, http.MethodGet, "/debug/magicsock", nil)
|
res, err := c.apiRequest(ctx, http.MethodGet, "/debug/magicsock", nil)
|
||||||
|
@ -365,7 +248,7 @@ func (c *WorkspaceAgentConn) DebugMagicsock(ctx context.Context) ([]byte, error)
|
||||||
return nil, xerrors.Errorf("do request: %w", err)
|
return nil, xerrors.Errorf("do request: %w", err)
|
||||||
}
|
}
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, ReadBodyAsError(res)
|
return nil, codersdk.ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
bs, err := io.ReadAll(res.Body)
|
bs, err := io.ReadAll(res.Body)
|
||||||
|
@ -377,7 +260,7 @@ func (c *WorkspaceAgentConn) DebugMagicsock(ctx context.Context) ([]byte, error)
|
||||||
|
|
||||||
// DebugManifest returns the agent's in-memory manifest. Unfortunately this must
|
// DebugManifest returns the agent's in-memory manifest. Unfortunately this must
|
||||||
// be returns as a []byte to avoid an import cycle.
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
res, err := c.apiRequest(ctx, http.MethodGet, "/debug/manifest", nil)
|
res, err := c.apiRequest(ctx, http.MethodGet, "/debug/manifest", nil)
|
||||||
|
@ -386,7 +269,7 @@ func (c *WorkspaceAgentConn) DebugManifest(ctx context.Context) ([]byte, error)
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, ReadBodyAsError(res)
|
return nil, codersdk.ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
bs, err := io.ReadAll(res.Body)
|
bs, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -396,7 +279,7 @@ func (c *WorkspaceAgentConn) DebugManifest(ctx context.Context) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLogs returns up to the last 10MB of `/tmp/coder-agent.log`
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
res, err := c.apiRequest(ctx, http.MethodGet, "/debug/logs", nil)
|
res, err := c.apiRequest(ctx, http.MethodGet, "/debug/logs", nil)
|
||||||
|
@ -405,7 +288,7 @@ func (c *WorkspaceAgentConn) DebugLogs(ctx context.Context) ([]byte, error) {
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, ReadBodyAsError(res)
|
return nil, codersdk.ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
bs, err := io.ReadAll(res.Body)
|
bs, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -415,7 +298,7 @@ func (c *WorkspaceAgentConn) DebugLogs(ctx context.Context) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrometheusMetrics returns a response from the agent's prometheus metrics endpoint
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
res, err := c.apiRequest(ctx, http.MethodGet, "/debug/prometheus", nil)
|
res, err := c.apiRequest(ctx, http.MethodGet, "/debug/prometheus", nil)
|
||||||
|
@ -424,7 +307,7 @@ func (c *WorkspaceAgentConn) PrometheusMetrics(ctx context.Context) ([]byte, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, ReadBodyAsError(res)
|
return nil, codersdk.ReadBodyAsError(res)
|
||||||
}
|
}
|
||||||
bs, err := io.ReadAll(res.Body)
|
bs, err := io.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -434,11 +317,11 @@ func (c *WorkspaceAgentConn) PrometheusMetrics(ctx context.Context) ([]byte, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// apiRequest makes a request to the workspace agent's HTTP API server.
|
// 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)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
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)
|
url := fmt.Sprintf("http://%s%s", host, path)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, url, body)
|
req, err := http.NewRequestWithContext(ctx, method, url, body)
|
||||||
|
@ -451,7 +334,7 @@ func (c *WorkspaceAgentConn) apiRequest(ctx context.Context, method, path string
|
||||||
|
|
||||||
// apiClient returns an HTTP client that can be used to make
|
// apiClient returns an HTTP client that can be used to make
|
||||||
// requests to the workspace agent's HTTP API server.
|
// requests to the workspace agent's HTTP API server.
|
||||||
func (c *WorkspaceAgentConn) apiClient() *http.Client {
|
func (c *AgentConn) apiClient() *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
// Disable keep alives as we're usually only making a single
|
// Disable keep alives as we're usually only making a single
|
||||||
|
@ -468,7 +351,7 @@ func (c *WorkspaceAgentConn) apiClient() *http.Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the port is TailnetStatisticsPort.
|
// 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)
|
return nil, xerrors.Errorf("request %q does not appear to be for http api", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,7 +364,7 @@ func (c *WorkspaceAgentConn) apiClient() *http.Client {
|
||||||
return nil, xerrors.Errorf("parse host addr: %w", err)
|
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 {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("dial http api: %w", err)
|
return nil, xerrors.Errorf("dial http api: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -492,6 +375,6 @@ func (c *WorkspaceAgentConn) apiClient() *http.Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WorkspaceAgentConn) GetPeerDiagnostics() tailnet.PeerDiagnostics {
|
func (c *AgentConn) GetPeerDiagnostics() tailnet.PeerDiagnostics {
|
||||||
return c.Conn.GetPeerDiagnostics(c.opts.AgentID)
|
return c.Conn.GetPeerDiagnostics(c.opts.AgentID)
|
||||||
}
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
package workspacesdk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
"nhooyr.io/websocket"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
|
||||||
|
"cdr.dev/slog"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/tailnet"
|
||||||
|
"github.com/coder/coder/v2/tailnet/proto"
|
||||||
|
"github.com/coder/retry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tailnetConn is the subset of the tailnet.Conn methods that tailnetAPIConnector uses. It is
|
||||||
|
// included so that we can fake it in testing.
|
||||||
|
//
|
||||||
|
// @typescript-ignore tailnetConn
|
||||||
|
type tailnetConn interface {
|
||||||
|
tailnet.Coordinatee
|
||||||
|
SetDERPMap(derpMap *tailcfg.DERPMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tailnetAPIConnector dials the tailnet API (v2+) and then uses the API with a tailnet.Conn to
|
||||||
|
//
|
||||||
|
// 1) run the Coordinate API and pass node information back and forth
|
||||||
|
// 2) stream DERPMap updates and program the Conn
|
||||||
|
//
|
||||||
|
// These functions share the same websocket, and so are combined here so that if we hit a problem
|
||||||
|
// we tear the whole thing down and start over with a new websocket.
|
||||||
|
//
|
||||||
|
// @typescript-ignore tailnetAPIConnector
|
||||||
|
type tailnetAPIConnector struct {
|
||||||
|
// We keep track of two contexts: the main context from the caller, and a "graceful" context
|
||||||
|
// that we keep open slightly longer than the main context to give a chance to send the
|
||||||
|
// Disconnect message to the coordinator. That tells the coordinator that we really meant to
|
||||||
|
// disconnect instead of just losing network connectivity.
|
||||||
|
ctx context.Context
|
||||||
|
gracefulCtx context.Context
|
||||||
|
cancelGracefulCtx context.CancelFunc
|
||||||
|
|
||||||
|
logger slog.Logger
|
||||||
|
|
||||||
|
agentID uuid.UUID
|
||||||
|
coordinateURL string
|
||||||
|
dialOptions *websocket.DialOptions
|
||||||
|
conn tailnetConn
|
||||||
|
|
||||||
|
connected chan error
|
||||||
|
isFirst bool
|
||||||
|
closed chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runTailnetAPIConnector creates and runs a tailnetAPIConnector
|
||||||
|
func runTailnetAPIConnector(
|
||||||
|
ctx context.Context, logger slog.Logger,
|
||||||
|
agentID uuid.UUID, coordinateURL string, dialOptions *websocket.DialOptions,
|
||||||
|
conn tailnetConn,
|
||||||
|
) *tailnetAPIConnector {
|
||||||
|
tac := &tailnetAPIConnector{
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
agentID: agentID,
|
||||||
|
coordinateURL: coordinateURL,
|
||||||
|
dialOptions: dialOptions,
|
||||||
|
conn: conn,
|
||||||
|
connected: make(chan error, 1),
|
||||||
|
closed: make(chan struct{}),
|
||||||
|
}
|
||||||
|
tac.gracefulCtx, tac.cancelGracefulCtx = context.WithCancel(context.Background())
|
||||||
|
go tac.manageGracefulTimeout()
|
||||||
|
go tac.run()
|
||||||
|
return tac
|
||||||
|
}
|
||||||
|
|
||||||
|
// manageGracefulTimeout allows the gracefulContext to last 1 second longer than the main context
|
||||||
|
// to allow a graceful disconnect.
|
||||||
|
func (tac *tailnetAPIConnector) manageGracefulTimeout() {
|
||||||
|
defer tac.cancelGracefulCtx()
|
||||||
|
<-tac.ctx.Done()
|
||||||
|
select {
|
||||||
|
case <-tac.closed:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tac *tailnetAPIConnector) run() {
|
||||||
|
tac.isFirst = true
|
||||||
|
defer close(tac.closed)
|
||||||
|
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); {
|
||||||
|
tailnetClient, err := tac.dial()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tac.logger.Debug(tac.ctx, "obtained tailnet API v2+ client")
|
||||||
|
tac.coordinateAndDERPMap(tailnetClient)
|
||||||
|
tac.logger.Debug(tac.ctx, "tailnet API v2+ connection lost")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) {
|
||||||
|
tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API")
|
||||||
|
// nolint:bodyclose
|
||||||
|
ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions)
|
||||||
|
if tac.isFirst {
|
||||||
|
if res != nil && res.StatusCode == http.StatusConflict {
|
||||||
|
err = codersdk.ReadBodyAsError(res)
|
||||||
|
tac.connected <- err
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tac.isFirst = false
|
||||||
|
close(tac.connected)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.Canceled) {
|
||||||
|
tac.logger.Error(tac.ctx, "failed to dial tailnet v2+ API", slog.Error(err))
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client, err := tailnet.NewDRPCClient(
|
||||||
|
websocket.NetConn(tac.gracefulCtx, ws, websocket.MessageBinary),
|
||||||
|
tac.logger,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
tac.logger.Debug(tac.ctx, "failed to create DRPCClient", slog.Error(err))
|
||||||
|
_ = ws.Close(websocket.StatusInternalError, "")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// coordinateAndDERPMap uses the provided client to coordinate and stream DERP Maps. It is combined
|
||||||
|
// into one function so that a problem with one tears down the other and triggers a retry (if
|
||||||
|
// appropriate). We multiplex both RPCs over the same websocket, so we want them to share the same
|
||||||
|
// fate.
|
||||||
|
func (tac *tailnetAPIConnector) coordinateAndDERPMap(client proto.DRPCTailnetClient) {
|
||||||
|
defer func() {
|
||||||
|
conn := client.DRPCConn()
|
||||||
|
closeErr := conn.Close()
|
||||||
|
if closeErr != nil &&
|
||||||
|
!xerrors.Is(closeErr, io.EOF) &&
|
||||||
|
!xerrors.Is(closeErr, context.Canceled) &&
|
||||||
|
!xerrors.Is(closeErr, context.DeadlineExceeded) {
|
||||||
|
tac.logger.Error(tac.ctx, "error closing DRPC connection", slog.Error(closeErr))
|
||||||
|
<-conn.Closed()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
tac.coordinate(client)
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
dErr := tac.derpMap(client)
|
||||||
|
if dErr != nil && tac.ctx.Err() == nil {
|
||||||
|
// The main context is still active, meaning that we want the tailnet data plane to stay
|
||||||
|
// up, even though we hit some error getting DERP maps on the control plane. That means
|
||||||
|
// we do NOT want to gracefully disconnect on the coordinate() routine. So, we'll just
|
||||||
|
// close the underlying connection. This will trigger a retry of the control plane in
|
||||||
|
// run().
|
||||||
|
client.DRPCConn().Close()
|
||||||
|
// Note that derpMap() logs it own errors, we don't bother here.
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tac *tailnetAPIConnector) coordinate(client proto.DRPCTailnetClient) {
|
||||||
|
// we use the gracefulCtx here so that we'll have time to send the graceful disconnect
|
||||||
|
coord, err := client.Coordinate(tac.gracefulCtx)
|
||||||
|
if err != nil {
|
||||||
|
tac.logger.Error(tac.ctx, "failed to connect to Coordinate RPC", slog.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
cErr := coord.Close()
|
||||||
|
if cErr != nil {
|
||||||
|
tac.logger.Debug(tac.ctx, "error closing Coordinate RPC", slog.Error(cErr))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
coordination := tailnet.NewRemoteCoordination(tac.logger, coord, tac.conn, tac.agentID)
|
||||||
|
tac.logger.Debug(tac.ctx, "serving coordinator")
|
||||||
|
select {
|
||||||
|
case <-tac.ctx.Done():
|
||||||
|
tac.logger.Debug(tac.ctx, "main context canceled; do graceful disconnect")
|
||||||
|
crdErr := coordination.Close()
|
||||||
|
if crdErr != nil {
|
||||||
|
tac.logger.Warn(tac.ctx, "failed to close remote coordination", slog.Error(err))
|
||||||
|
}
|
||||||
|
case err = <-coordination.Error():
|
||||||
|
if err != nil &&
|
||||||
|
!xerrors.Is(err, io.EOF) &&
|
||||||
|
!xerrors.Is(err, context.Canceled) &&
|
||||||
|
!xerrors.Is(err, context.DeadlineExceeded) {
|
||||||
|
tac.logger.Error(tac.ctx, "remote coordination error", slog.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tac *tailnetAPIConnector) derpMap(client proto.DRPCTailnetClient) error {
|
||||||
|
s, err := client.StreamDERPMaps(tac.ctx, &proto.StreamDERPMapsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("failed to connect to StreamDERPMaps RPC: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
cErr := s.Close()
|
||||||
|
if cErr != nil {
|
||||||
|
tac.logger.Debug(tac.ctx, "error closing StreamDERPMaps RPC", slog.Error(cErr))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
dmp, err := s.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tac.logger.Error(tac.ctx, "error receiving DERP Map", slog.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tac.logger.Debug(tac.ctx, "got new DERP Map", slog.F("derp_map", dmp))
|
||||||
|
dm := tailnet.DERPMapFromProto(dmp)
|
||||||
|
tac.conn.SetDERPMap(dm)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package codersdk
|
package workspacesdk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -17,6 +17,7 @@ import (
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"cdr.dev/slog/sloggers/slogtest"
|
"cdr.dev/slog/sloggers/slogtest"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/tailnet"
|
"github.com/coder/coder/v2/tailnet"
|
||||||
"github.com/coder/coder/v2/tailnet/proto"
|
"github.com/coder/coder/v2/tailnet/proto"
|
||||||
"github.com/coder/coder/v2/tailnet/tailnettest"
|
"github.com/coder/coder/v2/tailnet/tailnettest"
|
||||||
|
@ -50,7 +51,7 @@ func TestTailnetAPIConnector_Disconnects(t *testing.T) {
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
return
|
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{
|
err = svc.ServeConnV2(ctx, nc, tailnet.StreamID{
|
||||||
Name: "client",
|
Name: "client",
|
||||||
ID: clientID,
|
ID: clientID,
|
|
@ -1,4 +1,4 @@
|
||||||
package codersdk_test
|
package workspacesdk_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
|
@ -890,9 +890,9 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/con
|
||||||
|
|
||||||
### Responses
|
### Responses
|
||||||
|
|
||||||
| Status | Meaning | Description | Schema |
|
| Status | Meaning | Description | Schema |
|
||||||
| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------- |
|
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------ |
|
||||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.WorkspaceAgentConnectionInfo](schemas.md#codersdkworkspaceagentconnectioninfo) |
|
| 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).
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
|
|
@ -371,9 +371,9 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
||||||
|
|
||||||
### Responses
|
### Responses
|
||||||
|
|
||||||
| Status | Meaning | Description | Schema |
|
| Status | Meaning | Description | Schema |
|
||||||
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------ |
|
| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------------- |
|
||||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.HealthcheckReport](schemas.md#codersdkhealthcheckreport) |
|
| 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).
|
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
|
### Responses
|
||||||
|
|
||||||
| Status | Meaning | Description | Schema |
|
| Status | Meaning | Description | Schema |
|
||||||
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------ |
|
| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------- |
|
||||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.HealthSettings](schemas.md#codersdkhealthsettings) |
|
| 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).
|
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
|
### Parameters
|
||||||
|
|
||||||
| Name | In | Type | Required | Description |
|
| Name | In | Type | Required | Description |
|
||||||
| ------ | ---- | ------------------------------------------------------------------------ | -------- | ---------------------- |
|
| ------ | ---- | -------------------------------------------------------------------------- | -------- | ---------------------- |
|
||||||
| `body` | body | [codersdk.UpdateHealthSettings](schemas.md#codersdkupdatehealthsettings) | true | Update health settings |
|
| `body` | body | [healthsdk.UpdateHealthSettings](schemas.md#healthsdkupdatehealthsettings) | true | Update health settings |
|
||||||
|
|
||||||
### Example responses
|
### Example responses
|
||||||
|
|
||||||
|
@ -448,9 +448,9 @@ curl -X PUT http://coder-server:8080/api/v2/debug/health/settings \
|
||||||
|
|
||||||
### Responses
|
### Responses
|
||||||
|
|
||||||
| Status | Meaning | Description | Schema |
|
| Status | Meaning | Description | Schema |
|
||||||
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------------------------------ |
|
| ------ | ------------------------------------------------------- | ----------- | -------------------------------------------------------------------------- |
|
||||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UpdateHealthSettings](schemas.md#codersdkupdatehealthsettings) |
|
| 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).
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/audit"
|
||||||
"github.com/coder/coder/v2/enterprise/coderd"
|
"github.com/coder/coder/v2/enterprise/coderd"
|
||||||
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
||||||
|
@ -272,7 +273,7 @@ func TestAuditLogging(t *testing.T) {
|
||||||
DontAddLicense: true,
|
DontAddLicense: true,
|
||||||
})
|
})
|
||||||
r := setupWorkspaceAgent(t, client, user, 0)
|
r := setupWorkspaceAgent(t, client, user, 0)
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, r.sdkAgent.ID, nil) //nolint:gocritic // RBAC is not the purpose of this test
|
conn, err := workspacesdk.New(client).DialAgent(ctx, r.sdkAgent.ID, nil) //nolint:gocritic // RBAC is not the purpose of this test
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
connected := conn.AwaitReachable(ctx)
|
connected := conn.AwaitReachable(ctx)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/coderdenttest"
|
||||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
|
@ -82,10 +83,11 @@ func TestReplicas(t *testing.T) {
|
||||||
require.Len(t, replicas, 2)
|
require.Len(t, replicas, 2)
|
||||||
|
|
||||||
r := setupWorkspaceAgent(t, firstClient, firstUser, 0)
|
r := setupWorkspaceAgent(t, firstClient, firstUser, 0)
|
||||||
conn, err := secondClient.DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(secondClient).
|
||||||
BlockEndpoints: true,
|
DialAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialAgentOptions{
|
||||||
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
BlockEndpoints: true,
|
||||||
})
|
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitShort)
|
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||||
|
@ -128,10 +130,11 @@ func TestReplicas(t *testing.T) {
|
||||||
require.Len(t, replicas, 2)
|
require.Len(t, replicas, 2)
|
||||||
|
|
||||||
r := setupWorkspaceAgent(t, firstClient, firstUser, 0)
|
r := setupWorkspaceAgent(t, firstClient, firstUser, 0)
|
||||||
conn, err := secondClient.DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(secondClient).
|
||||||
BlockEndpoints: true,
|
DialAgent(context.Background(), r.sdkAgent.ID, &workspacesdk.DialAgentOptions{
|
||||||
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
BlockEndpoints: true,
|
||||||
})
|
Logger: slogtest.Make(t, nil).Named("client").Leveled(slog.LevelDebug),
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.IntervalSlow)
|
ctx, cancelFunc := context.WithTimeout(context.Background(), testutil.IntervalSlow)
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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/coderdenttest"
|
||||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||||
"github.com/coder/coder/v2/provisioner/echo"
|
"github.com/coder/coder/v2/provisioner/echo"
|
||||||
|
@ -46,7 +47,7 @@ func TestBlockNonBrowser(t *testing.T) {
|
||||||
})
|
})
|
||||||
r := setupWorkspaceAgent(t, client, user, 0)
|
r := setupWorkspaceAgent(t, client, user, 0)
|
||||||
//nolint:gocritic // Testing that even the owner gets blocked.
|
//nolint:gocritic // Testing that even the owner gets blocked.
|
||||||
_, err := client.DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, nil)
|
_, err := workspacesdk.New(client).DialAgent(context.Background(), r.sdkAgent.ID, nil)
|
||||||
var apiErr *codersdk.Error
|
var apiErr *codersdk.Error
|
||||||
require.ErrorAs(t, err, &apiErr)
|
require.ErrorAs(t, err, &apiErr)
|
||||||
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
||||||
|
@ -65,7 +66,7 @@ func TestBlockNonBrowser(t *testing.T) {
|
||||||
})
|
})
|
||||||
r := setupWorkspaceAgent(t, client, user, 0)
|
r := setupWorkspaceAgent(t, client, user, 0)
|
||||||
//nolint:gocritic // Testing RBAC is not the point of this test.
|
//nolint:gocritic // Testing RBAC is not the point of this test.
|
||||||
conn, err := client.DialWorkspaceAgent(context.Background(), r.sdkAgent.ID, nil)
|
conn, err := workspacesdk.New(client).DialAgent(context.Background(), r.sdkAgent.ID, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
agpltest "github.com/coder/coder/v2/tailnet/test"
|
agpltest "github.com/coder/coder/v2/tailnet/test"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -228,7 +228,7 @@ func TestPGCoordinatorSingle_AgentValidIPLegacy(t *testing.T) {
|
||||||
defer agent.close()
|
defer agent.close()
|
||||||
agent.sendNode(&agpl.Node{
|
agent.sendNode(&agpl.Node{
|
||||||
Addresses: []netip.Prefix{
|
Addresses: []netip.Prefix{
|
||||||
netip.PrefixFrom(codersdk.WorkspaceAgentIP, 128),
|
netip.PrefixFrom(workspacesdk.AgentIP, 128),
|
||||||
},
|
},
|
||||||
PreferredDERP: 10,
|
PreferredDERP: 10,
|
||||||
})
|
})
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/httpmw"
|
"github.com/coder/coder/v2/coderd/httpmw"
|
||||||
"github.com/coder/coder/v2/coderd/workspaceapps/apptest"
|
"github.com/coder/coder/v2/coderd/workspaceapps/apptest"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
"github.com/coder/coder/v2/cryptorand"
|
"github.com/coder/coder/v2/cryptorand"
|
||||||
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
||||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||||
|
@ -196,7 +197,7 @@ resourceLoop:
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitLong)
|
ctx := testutil.Context(t, testutil.WaitLong)
|
||||||
connInfo, err := client.WorkspaceAgentConnectionInfo(ctx, agentID)
|
connInfo, err := workspacesdk.New(client).AgentConnectionInfo(ctx, agentID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// There should be three DERP regions in the map: the primary, and each
|
// There should be three DERP regions in the map: the primary, and each
|
||||||
|
@ -269,7 +270,7 @@ resourceLoop:
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitLong)
|
ctx := testutil.Context(t, testutil.WaitLong)
|
||||||
connInfo, err := client.WorkspaceAgentConnectionInfo(ctx, agentID)
|
connInfo, err := workspacesdk.New(client).AgentConnectionInfo(ctx, agentID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, connInfo.DERPMap)
|
require.NotNil(t, connInfo.DERPMap)
|
||||||
require.Len(t, connInfo.DERPMap.Regions, 3+len(api.DeploymentValues.DERP.Server.STUNAddresses.Value()))
|
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)
|
_ = coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
||||||
|
|
||||||
// Connect to the workspace agent.
|
// Connect to the workspace agent.
|
||||||
conn, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: slogtest.Make(t, &slogtest.Options{
|
DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{
|
||||||
IgnoreErrors: true,
|
Logger: slogtest.Make(t, &slogtest.Options{
|
||||||
}).Named("client").Leveled(slog.LevelDebug),
|
IgnoreErrors: true,
|
||||||
// Force DERP.
|
}).Named("client").Leveled(slog.LevelDebug),
|
||||||
BlockEndpoints: true,
|
// Force DERP.
|
||||||
})
|
BlockEndpoints: true,
|
||||||
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err := conn.Close()
|
err := conn.Close()
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/httpmw"
|
"github.com/coder/coder/v2/coderd/httpmw"
|
||||||
"github.com/coder/coder/v2/coderd/workspaceapps"
|
"github.com/coder/coder/v2/coderd/workspaceapps"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
agpl "github.com/coder/coder/v2/tailnet"
|
agpl "github.com/coder/coder/v2/tailnet"
|
||||||
"github.com/coder/coder/v2/tailnet/proto"
|
"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
|
// DialWorkspaceAgent calls the underlying codersdk.Client's DialWorkspaceAgent
|
||||||
// method.
|
// method.
|
||||||
func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, options *codersdk.DialWorkspaceAgentOptions) (agentConn *codersdk.WorkspaceAgentConn, err error) {
|
func (c *Client) DialWorkspaceAgent(ctx context.Context, agentID uuid.UUID, options *workspacesdk.DialAgentOptions) (agentConn *workspacesdk.AgentConn, err error) {
|
||||||
return c.SDKClient.DialWorkspaceAgent(ctx, agentID, options)
|
return workspacesdk.New(c.SDKClient).DialAgent(ctx, agentID, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
type IssueSignedAppTokenResponse struct {
|
type IssueSignedAppTokenResponse struct {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"cdr.dev/slog/sloggers/sloghuman"
|
"cdr.dev/slog/sloggers/sloghuman"
|
||||||
"github.com/coder/coder/v2/coderd/tracing"
|
"github.com/coder/coder/v2/coderd/tracing"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/harness"
|
||||||
"github.com/coder/coder/v2/scaletest/loadtestutil"
|
"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...")
|
_, _ = fmt.Fprintln(logs, "\tUsing proxied DERP connection through coder server...")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := r.client.DialWorkspaceAgent(ctx, r.cfg.AgentID, &codersdk.DialWorkspaceAgentOptions{
|
conn, err := workspacesdk.New(r.client).
|
||||||
Logger: logger.Named("agentconn"),
|
DialAgent(ctx, r.cfg.AgentID, &workspacesdk.DialAgentOptions{
|
||||||
// If the config requested DERP, then force DERP.
|
Logger: logger.Named("agentconn"),
|
||||||
BlockEndpoints: r.cfg.ConnectionMode == ConnectionModeDerp,
|
// If the config requested DERP, then force DERP.
|
||||||
})
|
BlockEndpoints: r.cfg.ConnectionMode == ConnectionModeDerp,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("dial workspace agent: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForDisco(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn) error {
|
func waitForDisco(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error {
|
||||||
const pingAttempts = 10
|
const pingAttempts = 10
|
||||||
const pingDelay = 1 * time.Second
|
const pingDelay = 1 * time.Second
|
||||||
|
|
||||||
|
@ -163,7 +165,7 @@ func waitForDisco(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceA
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn) error {
|
func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error {
|
||||||
const directConnectionAttempts = 30
|
const directConnectionAttempts = 30
|
||||||
const directConnectionDelay = 1 * time.Second
|
const directConnectionDelay = 1 * time.Second
|
||||||
|
|
||||||
|
@ -205,7 +207,7 @@ func waitForDirectConnection(ctx context.Context, logs io.Writer, conn *codersdk
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyConnection(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn) error {
|
func verifyConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn) error {
|
||||||
const verifyConnectionAttempts = 30
|
const verifyConnectionAttempts = 30
|
||||||
const verifyConnectionDelay = 1 * time.Second
|
const verifyConnectionDelay = 1 * time.Second
|
||||||
|
|
||||||
|
@ -219,7 +221,7 @@ func verifyConnection(ctx context.Context, logs io.Writer, conn *codersdk.Worksp
|
||||||
|
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: net.JoinHostPort("localhost", strconv.Itoa(codersdk.WorkspaceAgentHTTPAPIServerPort)),
|
Host: net.JoinHostPort("localhost", strconv.Itoa(workspacesdk.AgentHTTPAPIServerPort)),
|
||||||
Path: "/",
|
Path: "/",
|
||||||
}
|
}
|
||||||
req, err := http.NewRequestWithContext(verifyCtx, http.MethodGet, u.String(), nil)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func performInitialConnections(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn, specs []Connection) error {
|
func performInitialConnections(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn, specs []Connection) error {
|
||||||
if len(specs) == 0 {
|
if len(specs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -285,7 +287,7 @@ func performInitialConnections(ctx context.Context, logs io.Writer, conn *coders
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func holdConnection(ctx context.Context, logs io.Writer, conn *codersdk.WorkspaceAgentConn, holdDur time.Duration, specs []Connection) error {
|
func holdConnection(ctx context.Context, logs io.Writer, conn *workspacesdk.AgentConn, holdDur time.Duration, specs []Connection) error {
|
||||||
ctx, span := tracing.StartSpan(ctx)
|
ctx, span := tracing.StartSpan(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -362,7 +364,7 @@ func holdConnection(ctx context.Context, logs io.Writer, conn *codersdk.Workspac
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func agentHTTPClient(conn *codersdk.WorkspaceAgentConn) *http.Client {
|
func agentHTTPClient(conn *workspacesdk.AgentConn) *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DisableKeepAlives: true,
|
DisableKeepAlives: true,
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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/provisioner/echo"
|
||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||||
"github.com/coder/coder/v2/scaletest/agentconn"
|
"github.com/coder/coder/v2/scaletest/agentconn"
|
||||||
|
@ -127,7 +128,7 @@ func Test_Runner(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ReconnectingPTY: &reconnectingpty.Config{
|
ReconnectingPTY: &reconnectingpty.Config{
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Height: 24,
|
Height: 24,
|
||||||
Width: 80,
|
Width: 80,
|
||||||
Command: "echo hello",
|
Command: "echo hello",
|
||||||
|
@ -416,7 +417,7 @@ func Test_Runner(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ReconnectingPTY: &reconnectingpty.Config{
|
ReconnectingPTY: &reconnectingpty.Config{
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Height: 24,
|
Height: 24,
|
||||||
Width: 80,
|
Width: 80,
|
||||||
Command: "echo hello",
|
Command: "echo hello",
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -23,7 +23,7 @@ type Config struct {
|
||||||
// If the ID is not set, defaults to a random UUID. If the width or height
|
// 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
|
// is not set, defaults to 80x24. If the command is not set, defaults to
|
||||||
// opening a login shell. Command runs in the default shell.
|
// opening a login shell. Command runs in the default shell.
|
||||||
Init codersdk.WorkspaceAgentReconnectingPTYInit `json:"init"`
|
Init workspacesdk.AgentReconnectingPTYInit `json:"init"`
|
||||||
// Timeout is the duration to wait for the command to exit. Defaults to
|
// Timeout is the duration to wait for the command to exit. Defaults to
|
||||||
// 5 minutes.
|
// 5 minutes.
|
||||||
Timeout httpapi.Duration `json:"timeout"`
|
Timeout httpapi.Duration `json:"timeout"`
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"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"
|
"github.com/coder/coder/v2/scaletest/reconnectingpty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ func Test_Config(t *testing.T) {
|
||||||
name: "OKFull",
|
name: "OKFull",
|
||||||
config: reconnectingpty.Config{
|
config: reconnectingpty.Config{
|
||||||
AgentID: id,
|
AgentID: id,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
ID: id,
|
ID: id,
|
||||||
Width: 80,
|
Width: 80,
|
||||||
Height: 24,
|
Height: 24,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"cdr.dev/slog/sloggers/sloghuman"
|
"cdr.dev/slog/sloggers/sloghuman"
|
||||||
"github.com/coder/coder/v2/coderd/tracing"
|
"github.com/coder/coder/v2/coderd/tracing"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/harness"
|
||||||
"github.com/coder/coder/v2/scaletest/loadtestutil"
|
"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, "\tHeight: %d\n", height)
|
||||||
_, _ = fmt.Fprintf(logs, "\tCommand: %q\n\n", r.cfg.Init.Command)
|
_, _ = fmt.Fprintf(logs, "\tCommand: %q\n\n", r.cfg.Init.Command)
|
||||||
|
|
||||||
conn, err := r.client.WorkspaceAgentReconnectingPTY(ctx, codersdk.WorkspaceAgentReconnectingPTYOpts{
|
conn, err := workspacesdk.New(r.client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{
|
||||||
AgentID: r.cfg.AgentID,
|
AgentID: r.cfg.AgentID,
|
||||||
Reconnect: id,
|
Reconnect: id,
|
||||||
Width: width,
|
Width: width,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"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/provisioner/echo"
|
||||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||||
"github.com/coder/coder/v2/scaletest/reconnectingpty"
|
"github.com/coder/coder/v2/scaletest/reconnectingpty"
|
||||||
|
@ -29,7 +30,7 @@ func Test_Runner(t *testing.T) {
|
||||||
|
|
||||||
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
// Use ; here because it's powershell compatible (vs &&).
|
// Use ; here because it's powershell compatible (vs &&).
|
||||||
Command: "echo 'hello world'; sleep 1",
|
Command: "echo 'hello world'; sleep 1",
|
||||||
},
|
},
|
||||||
|
@ -58,7 +59,7 @@ func Test_Runner(t *testing.T) {
|
||||||
|
|
||||||
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Command: "echo 'hello world'",
|
Command: "echo 'hello world'",
|
||||||
},
|
},
|
||||||
LogOutput: false,
|
LogOutput: false,
|
||||||
|
@ -86,7 +87,7 @@ func Test_Runner(t *testing.T) {
|
||||||
|
|
||||||
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Command: "echo 'hello world'",
|
Command: "echo 'hello world'",
|
||||||
},
|
},
|
||||||
Timeout: httpapi.Duration(2 * testutil.WaitSuperLong),
|
Timeout: httpapi.Duration(2 * testutil.WaitSuperLong),
|
||||||
|
@ -110,7 +111,7 @@ func Test_Runner(t *testing.T) {
|
||||||
|
|
||||||
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Command: "sleep 120",
|
Command: "sleep 120",
|
||||||
},
|
},
|
||||||
Timeout: httpapi.Duration(2 * time.Second),
|
Timeout: httpapi.Duration(2 * time.Second),
|
||||||
|
@ -139,7 +140,7 @@ func Test_Runner(t *testing.T) {
|
||||||
|
|
||||||
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Command: "sleep 120",
|
Command: "sleep 120",
|
||||||
},
|
},
|
||||||
Timeout: httpapi.Duration(2 * time.Second),
|
Timeout: httpapi.Duration(2 * time.Second),
|
||||||
|
@ -164,7 +165,7 @@ func Test_Runner(t *testing.T) {
|
||||||
|
|
||||||
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Command: "echo 'hello world'",
|
Command: "echo 'hello world'",
|
||||||
},
|
},
|
||||||
Timeout: httpapi.Duration(2 * testutil.WaitSuperLong),
|
Timeout: httpapi.Duration(2 * testutil.WaitSuperLong),
|
||||||
|
@ -194,7 +195,7 @@ func Test_Runner(t *testing.T) {
|
||||||
|
|
||||||
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Command: "echo 'hello world'; sleep 1",
|
Command: "echo 'hello world'; sleep 1",
|
||||||
},
|
},
|
||||||
ExpectOutput: "hello world",
|
ExpectOutput: "hello world",
|
||||||
|
@ -218,7 +219,7 @@ func Test_Runner(t *testing.T) {
|
||||||
|
|
||||||
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
runner := reconnectingpty.NewRunner(client, reconnectingpty.Config{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Init: codersdk.WorkspaceAgentReconnectingPTYInit{
|
Init: workspacesdk.AgentReconnectingPTYInit{
|
||||||
Command: "echo 'hello world'; sleep 1",
|
Command: "echo 'hello world'; sleep 1",
|
||||||
},
|
},
|
||||||
ExpectOutput: "bello borld",
|
ExpectOutput: "bello borld",
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"nhooyr.io/websocket"
|
"nhooyr.io/websocket"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
gossh "golang.org/x/crypto/ssh"
|
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) {
|
func connectRPTY(ctx context.Context, client *codersdk.Client, agentID, reconnect uuid.UUID, cmd string) (*countReadWriteCloser, error) {
|
||||||
width, height := 80, 25
|
width, height := 80, 25
|
||||||
conn, err := client.WorkspaceAgentReconnectingPTY(ctx, codersdk.WorkspaceAgentReconnectingPTYOpts{
|
conn, err := workspacesdk.New(client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
Reconnect: reconnect,
|
Reconnect: reconnect,
|
||||||
Width: uint16(width),
|
Width: uint16(width),
|
||||||
|
@ -107,7 +108,7 @@ func (c *rptyConn) writeNoLock(p []byte) (n int, err error) {
|
||||||
pp = p[:rptyJSONMaxDataSize]
|
pp = p[:rptyJSONMaxDataSize]
|
||||||
}
|
}
|
||||||
p = p[len(pp):]
|
p = p[len(pp):]
|
||||||
req := codersdk.ReconnectingPTYRequest{Data: string(pp)}
|
req := workspacesdk.ReconnectingPTYRequest{Data: string(pp)}
|
||||||
if err := c.wenc.Encode(req); err != nil {
|
if err := c.wenc.Encode(req); err != nil {
|
||||||
return n, xerrors.Errorf("encode pty request: %w", err)
|
return n, xerrors.Errorf("encode pty request: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -156,7 +157,7 @@ func connectSSH(ctx context.Context, client *codersdk.Client, agentID uuid.UUID,
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
agentConn, err := client.DialWorkspaceAgent(ctx, agentID, &codersdk.DialWorkspaceAgentOptions{})
|
agentConn, err := workspacesdk.New(client).DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("dial workspace agent: %w", err)
|
return nil, xerrors.Errorf("dial workspace agent: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// baseDirs are the directories to introspect for types to generate.
|
// 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
|
// externalTypes are types that are not in the baseDirs, but we want to
|
||||||
// support. These are usually types that are used in the baseDirs.
|
// support. These are usually types that are used in the baseDirs.
|
||||||
// Do not include things like "Database", as that would break the idea
|
// Do not include things like "Database", as that would break the idea
|
||||||
|
@ -357,7 +357,7 @@ type Maps struct {
|
||||||
|
|
||||||
// objName prepends the package name of a type if it is outside of codersdk.
|
// objName prepends the package name of a type if it is outside of codersdk.
|
||||||
func objName(obj types.Object) string {
|
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 cases.Title(language.English).String(pkgName) + obj.Name()
|
||||||
}
|
}
|
||||||
return obj.Name()
|
return obj.Name()
|
||||||
|
@ -879,7 +879,7 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
|
||||||
return TypescriptType{ValueType: "HealthMessage"}, nil
|
return TypescriptType{ValueType: "HealthMessage"}, nil
|
||||||
case "github.com/coder/coder/v2/coderd/healthcheck/health.Severity":
|
case "github.com/coder/coder/v2/coderd/healthcheck/health.Severity":
|
||||||
return TypescriptType{ValueType: "HealthSeverity"}, nil
|
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
|
return TypescriptType{ValueType: "HealthSection"}, nil
|
||||||
case "github.com/coder/coder/v2/codersdk.ProvisionerDaemon":
|
case "github.com/coder/coder/v2/codersdk.ProvisionerDaemon":
|
||||||
return TypescriptType{ValueType: "ProvisionerDaemon"}, nil
|
return TypescriptType{ValueType: "ProvisionerDaemon"}, nil
|
||||||
|
|
|
@ -27,19 +27,6 @@ export interface APIKeyWithOwner extends APIKey {
|
||||||
readonly username: string;
|
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
|
// From codersdk/licenses.go
|
||||||
export interface AddLicenseRequest {
|
export interface AddLicenseRequest {
|
||||||
readonly license: string;
|
readonly license: string;
|
||||||
|
@ -358,60 +345,12 @@ export interface DERPConfig {
|
||||||
readonly path: string;
|
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<number, DERPRegionReport>;
|
|
||||||
// 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
|
// From codersdk/workspaceagents.go
|
||||||
export interface DERPRegion {
|
export interface DERPRegion {
|
||||||
readonly preferred: boolean;
|
readonly preferred: boolean;
|
||||||
readonly latency_ms: number;
|
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
|
// From codersdk/deployment.go
|
||||||
export interface DERPServerConfig {
|
export interface DERPServerConfig {
|
||||||
readonly enable: boolean;
|
readonly enable: boolean;
|
||||||
|
@ -429,19 +368,6 @@ export interface DangerousConfig {
|
||||||
readonly allow_all_cors: boolean;
|
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
|
// From codersdk/workspaceagentportshare.go
|
||||||
export interface DeleteWorkspaceAgentPortShareRequest {
|
export interface DeleteWorkspaceAgentPortShareRequest {
|
||||||
readonly agent_name: string;
|
readonly agent_name: string;
|
||||||
|
@ -661,11 +587,6 @@ export interface Group {
|
||||||
readonly source: GroupSource;
|
readonly source: GroupSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/health.go
|
|
||||||
export interface HealthSettings {
|
|
||||||
readonly dismissed_healthchecks: HealthSection[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// From codersdk/workspaceapps.go
|
// From codersdk/workspaceapps.go
|
||||||
export interface Healthcheck {
|
export interface Healthcheck {
|
||||||
readonly url: string;
|
readonly url: string;
|
||||||
|
@ -679,21 +600,6 @@ export interface HealthcheckConfig {
|
||||||
readonly threshold_database: number;
|
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
|
// From codersdk/workspaceagents.go
|
||||||
export interface IssueReconnectingPTYSignedTokenRequest {
|
export interface IssueReconnectingPTYSignedTokenRequest {
|
||||||
readonly url: string;
|
readonly url: string;
|
||||||
|
@ -950,21 +856,6 @@ export interface ProvisionerDaemon {
|
||||||
readonly tags: Record<string, string>;
|
readonly tags: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// From codersdk/provisionerdaemons.go
|
||||||
export interface ProvisionerJob {
|
export interface ProvisionerJob {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
|
@ -1084,13 +975,6 @@ export interface SSHConfigResponse {
|
||||||
readonly ssh_config_options: Record<string, string>;
|
readonly ssh_config_options: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/health.go
|
|
||||||
export interface STUNReport {
|
|
||||||
readonly Enabled: boolean;
|
|
||||||
readonly CanSTUN: boolean;
|
|
||||||
readonly Error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// From codersdk/serversentevents.go
|
// From codersdk/serversentevents.go
|
||||||
export interface ServerSentEvent {
|
export interface ServerSentEvent {
|
||||||
readonly type: ServerSentEventType;
|
readonly type: ServerSentEventType;
|
||||||
|
@ -1399,11 +1283,6 @@ export interface UpdateCheckResponse {
|
||||||
readonly url: string;
|
readonly url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/health.go
|
|
||||||
export interface UpdateHealthSettings {
|
|
||||||
readonly dismissed_healthchecks: HealthSection[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// From codersdk/users.go
|
// From codersdk/users.go
|
||||||
export interface UpdateRoles {
|
export interface UpdateRoles {
|
||||||
readonly roles: string[];
|
readonly roles: string[];
|
||||||
|
@ -1619,17 +1498,6 @@ export interface VariableValue {
|
||||||
readonly value: string;
|
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
|
// From codersdk/workspaces.go
|
||||||
export interface Workspace {
|
export interface Workspace {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
|
@ -1702,14 +1570,14 @@ export interface WorkspaceAgentHealth {
|
||||||
readonly reason?: string;
|
readonly reason?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/workspaceagentconn.go
|
// From codersdk/workspaceagents.go
|
||||||
export interface WorkspaceAgentListeningPort {
|
export interface WorkspaceAgentListeningPort {
|
||||||
readonly process_name: string;
|
readonly process_name: string;
|
||||||
readonly network: string;
|
readonly network: string;
|
||||||
readonly port: number;
|
readonly port: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/workspaceagentconn.go
|
// From codersdk/workspaceagents.go
|
||||||
export interface WorkspaceAgentListeningPortsResponse {
|
export interface WorkspaceAgentListeningPortsResponse {
|
||||||
readonly ports: WorkspaceAgentListeningPort[];
|
readonly ports: WorkspaceAgentListeningPort[];
|
||||||
}
|
}
|
||||||
|
@ -1884,16 +1752,6 @@ export interface WorkspaceProxyBuildInfo {
|
||||||
readonly dashboard_url: string;
|
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<WorkspaceProxy>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// From codersdk/workspaceproxy.go
|
// From codersdk/workspaceproxy.go
|
||||||
export interface WorkspaceProxyStatus {
|
export interface WorkspaceProxyStatus {
|
||||||
readonly status: ProxyHealthStatus;
|
readonly status: ProxyHealthStatus;
|
||||||
|
@ -2080,23 +1938,6 @@ export const FeatureNames: FeatureName[] = [
|
||||||
export type GroupSource = "oidc" | "user";
|
export type GroupSource = "oidc" | "user";
|
||||||
export const GroupSources: 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
|
// From codersdk/insights.go
|
||||||
export type InsightsReportInterval = "day" | "week";
|
export type InsightsReportInterval = "day" | "week";
|
||||||
export const InsightsReportIntervals: InsightsReportInterval[] = [
|
export const InsightsReportIntervals: InsightsReportInterval[] = [
|
||||||
|
@ -2433,6 +2274,167 @@ export const WorkspaceTransitions: WorkspaceTransition[] = [
|
||||||
// From codersdk/workspaceproxy.go
|
// From codersdk/workspaceproxy.go
|
||||||
export type RegionTypes = Region | WorkspaceProxy;
|
export type RegionTypes = Region | WorkspaceProxy;
|
||||||
|
|
||||||
|
// The code below is generated from codersdk/healthsdk.
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface AccessURLReport {
|
||||||
|
readonly healthy: boolean;
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly warnings: HealthMessage[];
|
||||||
|
readonly dismissed: boolean;
|
||||||
|
readonly access_url: string;
|
||||||
|
readonly reachable: boolean;
|
||||||
|
readonly status_code: number;
|
||||||
|
readonly healthz_response: string;
|
||||||
|
readonly error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface DERPHealthReport {
|
||||||
|
readonly healthy: boolean;
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly warnings: HealthMessage[];
|
||||||
|
readonly dismissed: boolean;
|
||||||
|
readonly regions: Record<number, DERPRegionReport>;
|
||||||
|
// Named type "tailscale.com/net/netcheck.Report" unknown, using "any"
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
|
||||||
|
readonly netcheck?: any;
|
||||||
|
readonly netcheck_err?: string;
|
||||||
|
readonly netcheck_logs: string[];
|
||||||
|
readonly error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface DERPNodeReport {
|
||||||
|
readonly healthy: boolean;
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly warnings: HealthMessage[];
|
||||||
|
// Named type "tailscale.com/tailcfg.DERPNode" unknown, using "any"
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
|
||||||
|
readonly node?: any;
|
||||||
|
// Named type "tailscale.com/derp.ServerInfoMessage" unknown, using "any"
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
|
||||||
|
readonly node_info: any;
|
||||||
|
readonly can_exchange_messages: boolean;
|
||||||
|
readonly round_trip_ping: string;
|
||||||
|
readonly round_trip_ping_ms: number;
|
||||||
|
readonly uses_websocket: boolean;
|
||||||
|
readonly client_logs: string[][];
|
||||||
|
readonly client_errs: string[][];
|
||||||
|
readonly error?: string;
|
||||||
|
readonly stun: STUNReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface DERPRegionReport {
|
||||||
|
readonly healthy: boolean;
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly warnings: HealthMessage[];
|
||||||
|
// Named type "tailscale.com/tailcfg.DERPRegion" unknown, using "any"
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
|
||||||
|
readonly region?: any;
|
||||||
|
readonly node_reports: DERPNodeReport[];
|
||||||
|
readonly error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface DatabaseReport {
|
||||||
|
readonly healthy: boolean;
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly warnings: HealthMessage[];
|
||||||
|
readonly dismissed: boolean;
|
||||||
|
readonly reachable: boolean;
|
||||||
|
readonly latency: string;
|
||||||
|
readonly latency_ms: number;
|
||||||
|
readonly threshold_ms: number;
|
||||||
|
readonly error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface HealthSettings {
|
||||||
|
readonly dismissed_healthchecks: HealthSection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface HealthcheckReport {
|
||||||
|
readonly time: string;
|
||||||
|
readonly healthy: boolean;
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly failing_sections: HealthSection[];
|
||||||
|
readonly derp: DERPHealthReport;
|
||||||
|
readonly access_url: AccessURLReport;
|
||||||
|
readonly websocket: WebsocketReport;
|
||||||
|
readonly database: DatabaseReport;
|
||||||
|
readonly workspace_proxy: WorkspaceProxyReport;
|
||||||
|
readonly provisioner_daemons: ProvisionerDaemonsReport;
|
||||||
|
readonly coder_version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface ProvisionerDaemonsReport {
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly warnings: HealthMessage[];
|
||||||
|
readonly dismissed: boolean;
|
||||||
|
readonly error?: string;
|
||||||
|
readonly items: ProvisionerDaemonsReportItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface ProvisionerDaemonsReportItem {
|
||||||
|
readonly provisioner_daemon: ProvisionerDaemon;
|
||||||
|
readonly warnings: HealthMessage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface STUNReport {
|
||||||
|
readonly Enabled: boolean;
|
||||||
|
readonly CanSTUN: boolean;
|
||||||
|
readonly Error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface UpdateHealthSettings {
|
||||||
|
readonly dismissed_healthchecks: HealthSection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface WebsocketReport {
|
||||||
|
readonly healthy: boolean;
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly warnings: string[];
|
||||||
|
readonly dismissed: boolean;
|
||||||
|
readonly body: string;
|
||||||
|
readonly code: number;
|
||||||
|
readonly error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export interface WorkspaceProxyReport {
|
||||||
|
readonly healthy: boolean;
|
||||||
|
readonly severity: HealthSeverity;
|
||||||
|
readonly warnings: HealthMessage[];
|
||||||
|
readonly dismissed: boolean;
|
||||||
|
readonly error?: string;
|
||||||
|
readonly workspace_proxies: RegionsResponse<WorkspaceProxy>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From healthsdk/healthsdk.go
|
||||||
|
export type HealthSection =
|
||||||
|
| "AccessURL"
|
||||||
|
| "DERP"
|
||||||
|
| "Database"
|
||||||
|
| "ProvisionerDaemons"
|
||||||
|
| "Websocket"
|
||||||
|
| "WorkspaceProxy";
|
||||||
|
export const HealthSections: HealthSection[] = [
|
||||||
|
"AccessURL",
|
||||||
|
"DERP",
|
||||||
|
"Database",
|
||||||
|
"ProvisionerDaemons",
|
||||||
|
"Websocket",
|
||||||
|
"WorkspaceProxy",
|
||||||
|
];
|
||||||
|
|
||||||
// The code below is generated from coderd/healthcheck/health.
|
// The code below is generated from coderd/healthcheck/health.
|
||||||
|
|
||||||
// From health/model.go
|
// From health/model.go
|
||||||
|
|
|
@ -21,6 +21,8 @@ import (
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"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"
|
"github.com/coder/coder/v2/tailnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,16 +39,16 @@ type Bundle struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Deployment struct {
|
type Deployment struct {
|
||||||
BuildInfo *codersdk.BuildInfoResponse `json:"build"`
|
BuildInfo *codersdk.BuildInfoResponse `json:"build"`
|
||||||
Config *codersdk.DeploymentConfig `json:"config"`
|
Config *codersdk.DeploymentConfig `json:"config"`
|
||||||
Experiments codersdk.Experiments `json:"experiments"`
|
Experiments codersdk.Experiments `json:"experiments"`
|
||||||
HealthReport *codersdk.HealthcheckReport `json:"health_report"`
|
HealthReport *healthsdk.HealthcheckReport `json:"health_report"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Network struct {
|
type Network struct {
|
||||||
CoordinatorDebug string `json:"coordinator_debug"`
|
CoordinatorDebug string `json:"coordinator_debug"`
|
||||||
TailnetDebug string `json:"tailnet_debug"`
|
TailnetDebug string `json:"tailnet_debug"`
|
||||||
Netcheck *codersdk.WorkspaceAgentConnectionInfo `json:"netcheck"`
|
Netcheck *workspacesdk.AgentConnectionInfo `json:"netcheck"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Workspace struct {
|
type Workspace struct {
|
||||||
|
@ -110,7 +112,7 @@ func DeploymentInfo(ctx context.Context, client *codersdk.Client, log slog.Logge
|
||||||
})
|
})
|
||||||
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
hr, err := client.DebugHealth(ctx)
|
hr, err := healthsdk.New(client).DebugHealth(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("fetch health report: %w", err)
|
return xerrors.Errorf("fetch health report: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -173,7 +175,7 @@ func NetworkInfo(ctx context.Context, client *codersdk.Client, log slog.Logger,
|
||||||
log.Warn(ctx, "agent id required for agent connection info")
|
log.Warn(ctx, "agent id required for agent connection info")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
connInfo, err := client.WorkspaceAgentConnectionInfo(ctx, agentID)
|
connInfo, err := workspacesdk.New(client).AgentConnectionInfo(ctx, agentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("fetch agent conn info: %w", err)
|
return xerrors.Errorf("fetch agent conn info: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -331,10 +333,11 @@ func AgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, ag
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectedAgentInfo(ctx context.Context, client *codersdk.Client, log slog.Logger, agentID uuid.UUID, eg *errgroup.Group, a *Agent) (closer func()) {
|
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{
|
conn, err := workspacesdk.New(client).
|
||||||
Logger: log.Named("dial-agent"),
|
DialAgent(ctx, agentID, &workspacesdk.DialAgentOptions{
|
||||||
BlockEndpoints: false,
|
Logger: log.Named("dial-agent"),
|
||||||
})
|
BlockEndpoints: false,
|
||||||
|
})
|
||||||
|
|
||||||
closer = func() {}
|
closer = func() {}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
|
|
||||||
"cdr.dev/slog"
|
"cdr.dev/slog"
|
||||||
"cdr.dev/slog/sloggers/slogtest"
|
"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"
|
||||||
"github.com/coder/coder/v2/tailnet/proto"
|
"github.com/coder/coder/v2/tailnet/proto"
|
||||||
"github.com/coder/coder/v2/tailnet/tailnettest"
|
"github.com/coder/coder/v2/tailnet/tailnettest"
|
||||||
|
@ -155,7 +155,7 @@ func TestCoordinator(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
sendNode(&tailnet.Node{
|
sendNode(&tailnet.Node{
|
||||||
Addresses: []netip.Prefix{
|
Addresses: []netip.Prefix{
|
||||||
netip.PrefixFrom(codersdk.WorkspaceAgentIP, 128),
|
netip.PrefixFrom(workspacesdk.AgentIP, 128),
|
||||||
},
|
},
|
||||||
PreferredDERP: 10,
|
PreferredDERP: 10,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue