diff --git a/coderd/coderd.go b/coderd/coderd.go index 675d023e82194..65a044e3e9e7c 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -61,6 +61,7 @@ type Options struct { AccessURL *url.URL // AppHostname should be the wildcard hostname to use for workspace // applications INCLUDING the asterisk, (optional) suffix and leading dot. + // It will use the same scheme and port number as the access URL. // E.g. "*.apps.coder.com" or "*-apps.coder.com". AppHostname string // AppHostnameRegex contains the regex version of options.AppHostname as diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index e489a759c0255..24e1e13329a48 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -78,6 +78,10 @@ import ( ) type Options struct { + // AccessURL denotes a custom access URL. By default we use the httptest + // server's URL. Setting this may result in unexpected behavior (especially + // with running agents). + AccessURL *url.URL AppHostname string AWSCertificates awsidentity.Certificates Authorizer rbac.Authorizer @@ -144,7 +148,7 @@ func newWithCloser(t *testing.T, options *Options) (*codersdk.Client, io.Closer) return client, closer } -func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.CancelFunc, *coderd.Options) { +func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.CancelFunc, *url.URL, *coderd.Options) { if options == nil { options = &Options{} } @@ -214,6 +218,11 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can derpPort, err := strconv.Atoi(serverURL.Port()) require.NoError(t, err) + accessURL := options.AccessURL + if accessURL == nil { + accessURL = serverURL + } + stunAddr, stunCleanup := stuntest.ServeWithPacketListener(t, nettype.Std{}) t.Cleanup(stunCleanup) @@ -236,12 +245,12 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can mutex.Lock() defer mutex.Unlock() handler = h - }, cancelFunc, &coderd.Options{ + }, cancelFunc, serverURL, &coderd.Options{ AgentConnectionUpdateFrequency: 150 * time.Millisecond, // Force a long disconnection timeout to ensure // agents are not marked as disconnected during slow tests. AgentInactiveDisconnectTimeout: testutil.WaitShort, - AccessURL: serverURL, + AccessURL: accessURL, AppHostname: options.AppHostname, AppHostnameRegex: appHostnameRegex, Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), @@ -298,7 +307,7 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c if options == nil { options = &Options{} } - setHandler, cancelFunc, newOptions := NewOptions(t, options) + setHandler, cancelFunc, serverURL, newOptions := NewOptions(t, options) // We set the handler after server creation for the access URL. coderAPI := coderd.New(newOptions) setHandler(coderAPI.RootHandler) @@ -306,7 +315,7 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c if options.IncludeProvisionerDaemon { provisionerCloser = NewProvisionerDaemon(t, coderAPI) } - client := codersdk.New(coderAPI.AccessURL) + client := codersdk.New(serverURL) t.Cleanup(func() { cancelFunc() _ = provisionerCloser.Close() diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index 060a66870a889..380a02c3b0e73 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -58,7 +58,7 @@ var nonCanonicalHeaders = map[string]string{ func (api *API) appHost(rw http.ResponseWriter, r *http.Request) { host := api.AppHostname - if api.AccessURL.Port() != "" { + if host != "" && api.AccessURL.Port() != "" { host += fmt.Sprintf(":%s", api.AccessURL.Port()) } diff --git a/coderd/workspaceapps_test.go b/coderd/workspaceapps_test.go index 6b061181866eb..290876f9d52ce 100644 --- a/coderd/workspaceapps_test.go +++ b/coderd/workspaceapps_test.go @@ -47,17 +47,48 @@ const ( func TestGetAppHost(t *testing.T) { t.Parallel() - cases := []string{"", proxyTestSubdomainRaw} + cases := []struct { + name string + accessURL string + appHostname string + expected string + }{ + { + name: "OK", + accessURL: "https://test.coder.com", + appHostname: "*.test.coder.com", + expected: "*.test.coder.com", + }, + { + name: "None", + accessURL: "https://test.coder.com", + appHostname: "", + expected: "", + }, + { + name: "OKWithPort", + accessURL: "https://test.coder.com:8443", + appHostname: "*.test.coder.com", + expected: "*.test.coder.com:8443", + }, + { + name: "OKWithSuffix", + accessURL: "https://test.coder.com:8443", + appHostname: "*--suffix.test.coder.com", + expected: "*--suffix.test.coder.com:8443", + }, + } for _, c := range cases { c := c - name := c - if name == "" { - name = "Empty" - } - t.Run(name, func(t *testing.T) { + t.Run(c.name, func(t *testing.T) { t.Parallel() + + accessURL, err := url.Parse(c.accessURL) + require.NoError(t, err) + client := coderdtest.New(t, &coderdtest.Options{ - AppHostname: c, + AccessURL: accessURL, + AppHostname: c.appHostname, }) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) @@ -71,8 +102,7 @@ func TestGetAppHost(t *testing.T) { _ = coderdtest.CreateFirstUser(t, client) host, err = client.GetAppHost(ctx) require.NoError(t, err) - domain := strings.Split(host.Host, ":")[0] - require.Equal(t, c, domain) + require.Equal(t, c.expected, host.Host) }) } } diff --git a/enterprise/coderd/coderdenttest/coderdenttest.go b/enterprise/coderd/coderdenttest/coderdenttest.go index bf14d1acbc110..6ce147e7652d5 100644 --- a/enterprise/coderd/coderdenttest/coderdenttest.go +++ b/enterprise/coderd/coderdenttest/coderdenttest.go @@ -62,7 +62,7 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c if options.Options == nil { options.Options = &coderdtest.Options{} } - setHandler, cancelFunc, oop := coderdtest.NewOptions(t, options.Options) + setHandler, cancelFunc, serverURL, oop := coderdtest.NewOptions(t, options.Options) coderAPI, err := coderd.New(context.Background(), &coderd.Options{ RBAC: true, AuditLogging: options.AuditLogging, @@ -86,7 +86,7 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c _ = provisionerCloser.Close() _ = coderAPI.Close() }) - client := codersdk.New(coderAPI.AccessURL) + client := codersdk.New(serverURL) client.HTTPClient = &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{