Files
coder/enterprise/coderd/coderdenttest/proxytest.go
T
Spike Curtis bddb808b25 chore: arrange imports in a standard way (#21452)
Fixes all our Go file imports to match the preferred spec that we've _mostly_ been using. For example:

```
import (
	"context"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"golang.org/x/xerrors"
	"gopkg.in/natefinch/lumberjack.v2"

	"cdr.dev/slog/v3"
	"github.com/coder/coder/v2/codersdk/agentsdk"
	"github.com/coder/serpent"
)
```

3 groups: standard library, 3rd partly libs, Coder libs.

This PR makes the change across the codebase. The PR in the stack above modifies our formatting to maintain this state of affairs, and is a separate PR so it's possible to review that one in detail.
2026-01-08 15:24:11 +04:00

187 lines
5.0 KiB
Go

package coderdenttest
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/http/httptest"
"net/url"
"regexp"
"sync"
"testing"
"github.com/moby/moby/pkg/namesgenerator"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cdr.dev/slog/v3"
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd"
"github.com/coder/coder/v2/enterprise/wsproxy"
"github.com/coder/coder/v2/testutil"
)
type ProxyOptions struct {
Name string
Experiments codersdk.Experiments
TLSCertificates []tls.Certificate
AppHostname string
DisablePathApps bool
DerpDisabled bool
DerpOnly bool
BlockDirect bool
// ProxyURL is optional
ProxyURL *url.URL
// Token is optional. If specified, a new workspace proxy region will not be
// created, and the proxy will become a replica of the existing proxy
// region.
Token string
// ReplicaPingCallback is optional.
ReplicaPingCallback func(replicas []codersdk.Replica, err string)
// FlushStats is optional
FlushStats chan chan<- struct{}
}
type WorkspaceProxy struct {
*wsproxy.Server
ServerURL *url.URL
}
// NewWorkspaceProxyReplica will configure a wsproxy.Server with the given
// options. The new wsproxy replica will register itself with the given
// coderd.API instance.
//
// If a token is not provided, a new workspace proxy region is created using the
// owner client. If a token is provided, the proxy will become a replica of the
// existing proxy region.
func NewWorkspaceProxyReplica(t *testing.T, coderdAPI *coderd.API, owner *codersdk.Client, options *ProxyOptions) WorkspaceProxy {
t.Helper()
ctx, cancelFunc := context.WithCancel(context.Background())
t.Cleanup(cancelFunc)
if options == nil {
options = &ProxyOptions{}
}
// HTTP Server. We have to start this once to get the access URL to start
// the workspace proxy with. The workspace proxy has the handler, so the
// http server will start with a 503 until the proxy is started.
var mutex sync.RWMutex
var handler http.Handler
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mutex.RLock()
defer mutex.RUnlock()
if handler == nil {
http.Error(w, "handler not set", http.StatusServiceUnavailable)
return
}
handler.ServeHTTP(w, r)
}))
srv.Config.BaseContext = func(_ net.Listener) context.Context {
return ctx
}
if options.TLSCertificates != nil {
srv.TLS = &tls.Config{
Certificates: options.TLSCertificates,
MinVersion: tls.VersionTLS12,
}
srv.StartTLS()
} else {
srv.Start()
}
t.Cleanup(srv.Close)
tcpAddr, ok := srv.Listener.Addr().(*net.TCPAddr)
require.True(t, ok)
serverURL, err := url.Parse(srv.URL)
require.NoError(t, err)
serverURL.Host = fmt.Sprintf("127.0.0.1:%d", tcpAddr.Port)
accessURL := options.ProxyURL
if accessURL == nil {
accessURL = serverURL
}
var appHostnameRegex *regexp.Regexp
if options.AppHostname != "" {
var err error
appHostnameRegex, err = appurl.CompileHostnamePattern(options.AppHostname)
require.NoError(t, err)
}
if options.Name == "" {
options.Name = namesgenerator.GetRandomName(1)
}
token := options.Token
if token == "" {
proxyRes, err := owner.CreateWorkspaceProxy(ctx, codersdk.CreateWorkspaceProxyRequest{
Name: options.Name,
Icon: "/emojis/flag.png",
})
require.NoError(t, err, "failed to create workspace proxy")
token = proxyRes.ProxyToken
}
// Inherit collector options from coderd, but keep the wsproxy reporter.
statsCollectorOptions := coderdAPI.Options.WorkspaceAppsStatsCollectorOptions
statsCollectorOptions.Reporter = nil
if options.FlushStats != nil {
statsCollectorOptions.Flush = options.FlushStats
}
logger := testutil.Logger(t).With(slog.F("server_url", serverURL.String()))
wssrv, err := wsproxy.New(ctx, &wsproxy.Options{
Logger: logger,
Experiments: options.Experiments,
DashboardURL: coderdAPI.AccessURL,
AccessURL: accessURL,
AppHostname: options.AppHostname,
AppHostnameRegex: appHostnameRegex,
RealIPConfig: coderdAPI.RealIPConfig,
Tracing: coderdAPI.TracerProvider,
APIRateLimit: coderdAPI.APIRateLimit,
CookieConfig: coderdAPI.DeploymentValues.HTTPCookies,
ProxySessionToken: token,
DisablePathApps: options.DisablePathApps,
// We need a new registry to not conflict with the coderd internal
// proxy metrics.
PrometheusRegistry: prometheus.NewRegistry(),
DERPEnabled: !options.DerpDisabled,
DERPOnly: options.DerpOnly,
DERPServerRelayAddress: serverURL.String(),
ReplicaErrCallback: options.ReplicaPingCallback,
StatsCollectorOptions: statsCollectorOptions,
BlockDirect: options.BlockDirect,
})
require.NoError(t, err)
t.Cleanup(func() {
err := wssrv.Close()
assert.NoError(t, err)
})
mutex.Lock()
handler = wssrv.Handler
mutex.Unlock()
return WorkspaceProxy{
Server: wssrv,
ServerURL: serverURL,
}
}