mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
Adds a new hidden subcommand `coder connect exists <hostname>` that checks if the name exists via Coder Connect. This will be used in SSH config to match only if Coder Connect is unavailable for the hostname in question, so that the SSH client will directly dial the workspace over an existing Coder Connect tunnel. Also refactors the way we inject a test DNS resolver into the lookup functions so that we can test from outside the `workspacesdk` package.
149 lines
4.5 KiB
Go
149 lines
4.5 KiB
Go
package workspacesdk_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/xerrors"
|
|
"tailscale.com/net/tsaddr"
|
|
"tailscale.com/tailcfg"
|
|
|
|
"github.com/coder/websocket"
|
|
|
|
"github.com/coder/coder/v2/coderd/httpapi"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/codersdk/agentsdk"
|
|
"github.com/coder/coder/v2/codersdk/workspacesdk"
|
|
"github.com/coder/coder/v2/tailnet"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
func TestWorkspaceRewriteDERPMap(t *testing.T) {
|
|
t.Parallel()
|
|
// This test ensures that RewriteDERPMap mutates built-in DERPs with the
|
|
// client access URL.
|
|
dm := &tailcfg.DERPMap{
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
1: {
|
|
EmbeddedRelay: true,
|
|
RegionID: 1,
|
|
Nodes: []*tailcfg.DERPNode{{
|
|
HostName: "bananas.org",
|
|
DERPPort: 1,
|
|
}},
|
|
},
|
|
},
|
|
}
|
|
parsed, err := url.Parse("https://coconuts.org:44558")
|
|
require.NoError(t, err)
|
|
client := agentsdk.New(parsed)
|
|
client.RewriteDERPMap(dm)
|
|
region := dm.Regions[1]
|
|
require.True(t, region.EmbeddedRelay)
|
|
require.Len(t, region.Nodes, 1)
|
|
node := region.Nodes[0]
|
|
require.Equal(t, "coconuts.org", node.HostName)
|
|
require.Equal(t, 44558, node.DERPPort)
|
|
}
|
|
|
|
func TestWorkspaceDialerFailure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Setup.
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
logger := testutil.Logger(t)
|
|
|
|
// Given: a mock HTTP server which mimicks an unreachable database when calling the coordination endpoint.
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
|
|
Message: codersdk.DatabaseNotReachable,
|
|
Detail: "oops",
|
|
})
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
|
|
u, err := url.Parse(srv.URL)
|
|
require.NoError(t, err)
|
|
|
|
// When: calling the coordination endpoint.
|
|
dialer := workspacesdk.NewWebsocketDialer(logger, u, &websocket.DialOptions{})
|
|
_, err = dialer.Dial(ctx, nil)
|
|
|
|
// Then: an error indicating a database issue is returned, to conditionalize the behavior of the caller.
|
|
require.ErrorIs(t, err, codersdk.ErrDatabaseNotReachable)
|
|
}
|
|
|
|
func TestClient_IsCoderConnectRunning(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "/api/v2/workspaceagents/connection", r.URL.Path)
|
|
httpapi.Write(ctx, rw, http.StatusOK, workspacesdk.AgentConnectionInfo{
|
|
HostnameSuffix: "test",
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
apiURL, err := url.Parse(srv.URL)
|
|
require.NoError(t, err)
|
|
sdkClient := codersdk.New(apiURL)
|
|
client := workspacesdk.New(sdkClient)
|
|
|
|
// Right name, right IP
|
|
expectedName := fmt.Sprintf(tailnet.IsCoderConnectEnabledFmtString, "test")
|
|
ctxResolveExpected := workspacesdk.WithTestOnlyCoderContextResolver(ctx,
|
|
&fakeResolver{t: t, hostMap: map[string][]net.IP{
|
|
expectedName: {net.ParseIP(tsaddr.CoderServiceIPv6().String())},
|
|
}})
|
|
|
|
result, err := client.IsCoderConnectRunning(ctxResolveExpected, workspacesdk.CoderConnectQueryOptions{})
|
|
require.NoError(t, err)
|
|
require.True(t, result)
|
|
|
|
// Wrong name
|
|
result, err = client.IsCoderConnectRunning(ctxResolveExpected, workspacesdk.CoderConnectQueryOptions{HostnameSuffix: "coder"})
|
|
require.NoError(t, err)
|
|
require.False(t, result)
|
|
|
|
// Not found
|
|
ctxResolveNotFound := workspacesdk.WithTestOnlyCoderContextResolver(ctx,
|
|
&fakeResolver{t: t, err: &net.DNSError{IsNotFound: true}})
|
|
result, err = client.IsCoderConnectRunning(ctxResolveNotFound, workspacesdk.CoderConnectQueryOptions{})
|
|
require.NoError(t, err)
|
|
require.False(t, result)
|
|
|
|
// Some other error
|
|
ctxResolverErr := workspacesdk.WithTestOnlyCoderContextResolver(ctx,
|
|
&fakeResolver{t: t, err: xerrors.New("a bad thing happened")})
|
|
_, err = client.IsCoderConnectRunning(ctxResolverErr, workspacesdk.CoderConnectQueryOptions{})
|
|
require.Error(t, err)
|
|
|
|
// Right name, wrong IP
|
|
ctxResolverWrongIP := workspacesdk.WithTestOnlyCoderContextResolver(ctx,
|
|
&fakeResolver{t: t, hostMap: map[string][]net.IP{
|
|
expectedName: {net.ParseIP("2001::34")},
|
|
}})
|
|
result, err = client.IsCoderConnectRunning(ctxResolverWrongIP, workspacesdk.CoderConnectQueryOptions{})
|
|
require.NoError(t, err)
|
|
require.False(t, result)
|
|
}
|
|
|
|
type fakeResolver struct {
|
|
t testing.TB
|
|
hostMap map[string][]net.IP
|
|
err error
|
|
}
|
|
|
|
func (f *fakeResolver) LookupIP(_ context.Context, network, host string) ([]net.IP, error) {
|
|
assert.Equal(f.t, "ip6", network)
|
|
return f.hostMap[host], f.err
|
|
}
|