mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
chore: add workspace proxies to the backend (#7032)
Co-authored-by: Dean Sheather <dean@deansheather.com>
This commit is contained in:
@ -58,10 +58,15 @@ type DeploymentOptions struct {
|
||||
type Deployment struct {
|
||||
Options *DeploymentOptions
|
||||
|
||||
// Client should be logged in as the admin user.
|
||||
Client *codersdk.Client
|
||||
// SDKClient should be logged in as the admin user.
|
||||
SDKClient *codersdk.Client
|
||||
FirstUser codersdk.CreateFirstUserResponse
|
||||
PathAppBaseURL *url.URL
|
||||
|
||||
// AppHostIsPrimary is true if the app host is also the primary coder API
|
||||
// server. This disables any tests that test API passthrough or rely on the
|
||||
// app server not being the API server.
|
||||
AppHostIsPrimary bool
|
||||
}
|
||||
|
||||
// DeploymentFactory generates a deployment with an API client, a path base URL,
|
||||
@ -83,8 +88,8 @@ type App struct {
|
||||
Query string
|
||||
}
|
||||
|
||||
// AppDetails are the full test details returned from setupProxyTestWithFactory.
|
||||
type AppDetails struct {
|
||||
// Details are the full test details returned from setupProxyTestWithFactory.
|
||||
type Details struct {
|
||||
*Deployment
|
||||
|
||||
Me codersdk.User
|
||||
@ -96,15 +101,33 @@ type AppDetails struct {
|
||||
Agent *codersdk.WorkspaceAgent
|
||||
AppPort uint16
|
||||
|
||||
FakeApp App
|
||||
OwnerApp App
|
||||
AuthenticatedApp App
|
||||
PublicApp App
|
||||
PortApp App
|
||||
Apps struct {
|
||||
Fake App
|
||||
Owner App
|
||||
Authenticated App
|
||||
Public App
|
||||
Port App
|
||||
}
|
||||
}
|
||||
|
||||
// AppClient returns a *codersdk.Client that will route all requests to the
|
||||
// app server. API requests will fail with this client. Any redirect responses
|
||||
// are not followed by default.
|
||||
//
|
||||
// The client is authenticated as the first user by default.
|
||||
func (d *Details) AppClient(t *testing.T) *codersdk.Client {
|
||||
client := codersdk.New(d.PathAppBaseURL)
|
||||
client.SetSessionToken(d.SDKClient.SessionToken())
|
||||
forceURLTransport(t, client)
|
||||
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// PathAppURL returns the URL for the given path app.
|
||||
func (d *AppDetails) PathAppURL(app App) *url.URL {
|
||||
func (d *Details) PathAppURL(app App) *url.URL {
|
||||
appPath := fmt.Sprintf("/@%s/%s/apps/%s", app.Username, app.WorkspaceName, app.AppSlugOrPort)
|
||||
|
||||
u := *d.PathAppBaseURL
|
||||
@ -115,11 +138,7 @@ func (d *AppDetails) PathAppURL(app App) *url.URL {
|
||||
}
|
||||
|
||||
// SubdomainAppURL returns the URL for the given subdomain app.
|
||||
func (d *AppDetails) SubdomainAppURL(app App) *url.URL {
|
||||
if d.Options.DisableSubdomainApps || d.Options.AppHost == "" {
|
||||
panic("subdomain apps are disabled")
|
||||
}
|
||||
|
||||
func (d *Details) SubdomainAppURL(app App) *url.URL {
|
||||
host := fmt.Sprintf("%s--%s--%s--%s", app.AppSlugOrPort, app.AgentName, app.WorkspaceName, app.Username)
|
||||
|
||||
u := *d.PathAppBaseURL
|
||||
@ -135,7 +154,7 @@ func (d *AppDetails) SubdomainAppURL(app App) *url.URL {
|
||||
// 3. Create a template version, template and workspace with many apps.
|
||||
// 4. Start a workspace agent.
|
||||
// 5. Returns details about the deployment and its apps.
|
||||
func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *DeploymentOptions) *AppDetails {
|
||||
func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *DeploymentOptions) *Details {
|
||||
if opts == nil {
|
||||
opts = &DeploymentOptions{}
|
||||
}
|
||||
@ -150,19 +169,19 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De
|
||||
|
||||
// Configure the HTTP client to not follow redirects and to route all
|
||||
// requests regardless of hostname to the coderd test server.
|
||||
deployment.Client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
deployment.SDKClient.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
forceURLTransport(t, deployment.Client)
|
||||
forceURLTransport(t, deployment.SDKClient)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
||||
defer cancel()
|
||||
|
||||
me, err := deployment.Client.User(ctx, codersdk.Me)
|
||||
me, err := deployment.SDKClient.User(ctx, codersdk.Me)
|
||||
require.NoError(t, err)
|
||||
|
||||
if opts.noWorkspace {
|
||||
return &AppDetails{
|
||||
return &Details{
|
||||
Deployment: deployment,
|
||||
Me: me,
|
||||
}
|
||||
@ -171,49 +190,51 @@ func setupProxyTestWithFactory(t *testing.T, factory DeploymentFactory, opts *De
|
||||
if opts.port == 0 {
|
||||
opts.port = appServer(t)
|
||||
}
|
||||
workspace, agnt := createWorkspaceWithApps(t, deployment.Client, deployment.FirstUser.OrganizationID, me, opts.AppHost, opts.port)
|
||||
workspace, agnt := createWorkspaceWithApps(t, deployment.SDKClient, deployment.FirstUser.OrganizationID, me, opts.port)
|
||||
|
||||
return &AppDetails{
|
||||
details := &Details{
|
||||
Deployment: deployment,
|
||||
Me: me,
|
||||
Workspace: &workspace,
|
||||
Agent: &agnt,
|
||||
AppPort: opts.port,
|
||||
|
||||
FakeApp: App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: proxyTestAppNameFake,
|
||||
},
|
||||
OwnerApp: App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: proxyTestAppNameOwner,
|
||||
Query: proxyTestAppQuery,
|
||||
},
|
||||
AuthenticatedApp: App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: proxyTestAppNameAuthenticated,
|
||||
Query: proxyTestAppQuery,
|
||||
},
|
||||
PublicApp: App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: proxyTestAppNamePublic,
|
||||
Query: proxyTestAppQuery,
|
||||
},
|
||||
PortApp: App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: strconv.Itoa(int(opts.port)),
|
||||
},
|
||||
}
|
||||
|
||||
details.Apps.Fake = App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: proxyTestAppNameFake,
|
||||
}
|
||||
details.Apps.Owner = App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: proxyTestAppNameOwner,
|
||||
Query: proxyTestAppQuery,
|
||||
}
|
||||
details.Apps.Authenticated = App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: proxyTestAppNameAuthenticated,
|
||||
Query: proxyTestAppQuery,
|
||||
}
|
||||
details.Apps.Public = App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: proxyTestAppNamePublic,
|
||||
Query: proxyTestAppQuery,
|
||||
}
|
||||
details.Apps.Port = App{
|
||||
Username: me.Username,
|
||||
WorkspaceName: workspace.Name,
|
||||
AgentName: agnt.Name,
|
||||
AppSlugOrPort: strconv.Itoa(int(opts.port)),
|
||||
}
|
||||
|
||||
return details
|
||||
}
|
||||
|
||||
func appServer(t *testing.T) uint16 {
|
||||
@ -259,7 +280,7 @@ func appServer(t *testing.T) uint16 {
|
||||
return uint16(tcpAddr.Port)
|
||||
}
|
||||
|
||||
func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.UUID, me codersdk.User, appHost string, port uint16, workspaceMutators ...func(*codersdk.CreateWorkspaceRequest)) (codersdk.Workspace, codersdk.WorkspaceAgent) {
|
||||
func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.UUID, me codersdk.User, port uint16, workspaceMutators ...func(*codersdk.CreateWorkspaceRequest)) (codersdk.Workspace, codersdk.WorkspaceAgent) {
|
||||
authToken := uuid.NewString()
|
||||
|
||||
appURL := fmt.Sprintf("http://127.0.0.1:%d?%s", port, proxyTestAppQuery)
|
||||
@ -318,7 +339,18 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
|
||||
|
||||
agentClient := agentsdk.New(client.URL)
|
||||
agentClient.SetSessionToken(authToken)
|
||||
if appHost != "" {
|
||||
|
||||
// TODO (@dean): currently, the primary app host is used when generating
|
||||
// the port URL we tell the agent to use. We don't have any plans to change
|
||||
// that until we let templates pick which proxy they want to use in the
|
||||
// terraform.
|
||||
//
|
||||
// This means that all port URLs generated in code-server etc. will be sent
|
||||
// to the primary.
|
||||
appHostCtx := testutil.Context(t, testutil.WaitLong)
|
||||
primaryAppHost, err := client.AppHost(appHostCtx)
|
||||
require.NoError(t, err)
|
||||
if primaryAppHost.Host != "" {
|
||||
manifest, err := agentClient.Manifest(context.Background())
|
||||
require.NoError(t, err)
|
||||
proxyURL := fmt.Sprintf(
|
||||
@ -326,11 +358,8 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U
|
||||
proxyTestAgentName,
|
||||
workspace.Name,
|
||||
me.Username,
|
||||
strings.ReplaceAll(appHost, "*", ""),
|
||||
strings.ReplaceAll(primaryAppHost.Host, "*", ""),
|
||||
)
|
||||
if client.URL.Port() != "" {
|
||||
proxyURL += fmt.Sprintf(":%s", client.URL.Port())
|
||||
}
|
||||
require.Equal(t, proxyURL, manifest.VSCodePortProxyURI)
|
||||
}
|
||||
agentCloser := agent.New(agent.Options{
|
||||
@ -386,7 +415,7 @@ func requestWithRetries(ctx context.Context, t require.TestingT, client *codersd
|
||||
}
|
||||
|
||||
// forceURLTransport forces the client to route all requests to the client's
|
||||
// configured URL host regardless of hostname.
|
||||
// configured URLs host regardless of hostname.
|
||||
func forceURLTransport(t *testing.T, client *codersdk.Client) {
|
||||
defaultTransport, ok := http.DefaultTransport.(*http.Transport)
|
||||
require.True(t, ok)
|
||||
|
Reference in New Issue
Block a user