-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(cli): request path-app workspace-traffic URL with trailing slash #29245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
The scaletest workspace-traffic app runs failed the WebSocket handshake because the constructed path-app URL lacked a trailing slash. coderd and workspace proxies 307-redirect /apps/<slug> to /apps/<slug>/, and the scaletest client rejects redirects, so the dial failed on all regions. A probe (exp scaletest workspace-traffic-probe) confirmed no-slash fails and slash succeeds across primary/europe/asia.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -288,7 +288,12 @@ func createWorkspaceAppConfig(client *codersdk.Client, appHost, app string, work | |
|
|
||
| c.URL = fmt.Sprintf("%s://%s", client.URL.Scheme, strings.Replace(appHost, "*", agent.Apps[i].SubdomainName, 1)) | ||
| } else { | ||
| c.URL = fmt.Sprintf("%s/@%s/%s.%s/apps/%s", client.URL.String(), workspace.OwnerName, workspace.Name, agent.Name, agent.Apps[i].Slug) | ||
| // Path-based apps are served at a trailing-slash URL: coderd (and | ||
| // workspace proxies) 307-redirect "/apps/<slug>" to "/apps/<slug>/" | ||
| // (see coderd/workspaceapps/proxy.go). The scaletest client rejects | ||
| // redirects, so the WebSocket handshake fails on the redirect unless we | ||
| // request the already-normalized URL. | ||
| c.URL = fmt.Sprintf("%s/@%s/%s.%s/apps/%s/", client.URL.String(), workspace.OwnerName, workspace.Name, agent.Name, agent.Apps[i].Slug) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3 [CRF-2] Building the URL with
Verified:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note [CRF-8] Path-app URL construction is now duplicated. (Kite Note, Pariston Note, Ryosuke Note) The route shape and the trailing-slash normalization are encoded here, in
|
||
| } | ||
|
|
||
| return c, nil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| //go:build !slim | ||
|
|
||
| package cli | ||
|
|
||
| import ( | ||
| "net/url" | ||
| "testing" | ||
|
|
||
| "github.com/google/uuid" | ||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "github.com/coder/coder/v2/codersdk" | ||
| ) | ||
|
|
||
| func TestCreateWorkspaceAppConfig(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| newClient := func(t *testing.T, rawURL string) *codersdk.Client { | ||
| t.Helper() | ||
| u, err := url.Parse(rawURL) | ||
| require.NoError(t, err) | ||
| return codersdk.New(u) | ||
| } | ||
|
|
||
| ws := codersdk.Workspace{ | ||
| ID: uuid.New(), | ||
| Name: "myws", | ||
| OwnerName: "alice", | ||
| } | ||
|
|
||
| t.Run("Empty", func(t *testing.T) { | ||
| t.Parallel() | ||
| cfg, err := createWorkspaceAppConfig(newClient(t, "https://coder.example.com"), "", "", ws, codersdk.WorkspaceAgent{Name: "main"}) | ||
| require.NoError(t, err) | ||
| require.Empty(t, cfg.Name) | ||
| require.Empty(t, cfg.URL) | ||
| }) | ||
|
|
||
| t.Run("NotFound", func(t *testing.T) { | ||
| t.Parallel() | ||
| agent := codersdk.WorkspaceAgent{Name: "main"} | ||
| _, err := createWorkspaceAppConfig(newClient(t, "https://coder.example.com"), "", "missing", ws, agent) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit [CRF-5] Error-path subtests assert only
Use
|
||
| require.Error(t, err) | ||
| }) | ||
|
|
||
| t.Run("PathAppHasTrailingSlash", func(t *testing.T) { | ||
| t.Parallel() | ||
| agent := codersdk.WorkspaceAgent{ | ||
| Name: "main", | ||
| Apps: []codersdk.WorkspaceApp{{Slug: "wsec", Subdomain: false}}, | ||
| } | ||
| cfg, err := createWorkspaceAppConfig(newClient(t, "https://coder.example.com"), "*.apps.example.com", "wsec", ws, agent) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note [CRF-9] The test pins the URL string, not the redirect behavior it exists to protect. (Bisky Note, Pariston Note, Chopper Note) The string assertion is the right guard for a URL builder and fails loudly if the slash is dropped. But the causal link (trailing slash -> no redirect -> handshake succeeds) was only ever verified by the now-removed probe. If coderd's normalization branch changes, this suite stays green while the dial breaks again. A full integration dial is heavy and likely not justified; flagging the coverage boundary, not asking for the test.
|
||
| require.NoError(t, err) | ||
| require.Equal(t, "wsec", cfg.Name) | ||
| // The trailing slash avoids coderd's path-app normalization redirect, | ||
| // which the scaletest client rejects. | ||
| require.Equal(t, "https://coder.example.com/@alice/myws.main/apps/wsec/", cfg.URL) | ||
| }) | ||
|
|
||
| t.Run("SubdomainApp", func(t *testing.T) { | ||
| t.Parallel() | ||
| agent := codersdk.WorkspaceAgent{ | ||
| Name: "main", | ||
| Apps: []codersdk.WorkspaceApp{{Slug: "wsec", Subdomain: true, SubdomainName: "wsec--main--myws--alice"}}, | ||
| } | ||
| cfg, err := createWorkspaceAppConfig(newClient(t, "https://coder.example.com"), "*.apps.example.com", "wsec", ws, agent) | ||
| require.NoError(t, err) | ||
| require.Equal(t, "https://wsec--main--myws--alice.apps.example.com", cfg.URL) | ||
| }) | ||
|
|
||
| t.Run("SubdomainAppRequiresAppHost", func(t *testing.T) { | ||
| t.Parallel() | ||
| agent := codersdk.WorkspaceAgent{ | ||
| Name: "main", | ||
| Apps: []codersdk.WorkspaceApp{{Slug: "wsec", Subdomain: true, SubdomainName: "wsec--main--myws--alice"}}, | ||
| } | ||
| _, err := createWorkspaceAppConfig(newClient(t, "https://coder.example.com"), "", "wsec", ws, agent) | ||
| require.Error(t, err) | ||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3 [CRF-1] The trailing slash only defeats coderd's
path == ""normalization redirect; a path app whose configured URL carries default query params still 307-redirects and fails the dial. (Mafuuu P3, Hisoka Note)Verified: both branches exist.
rejectRedirect(cli/root.go, absent--allow-redirects) rejects that 307, so--apppointed at a query-bearing path app re-breaks the exact way this PR set out to fix. The scaletest'swsececho app has no default query, which is why every probe row wentws=ok, so the common path works. The in-code comment at line 291 is accurate; the PR body's "avoids the redirect entirely" overstates it. Narrow the claim to the path-normalization redirect and note the query-default case, or follow redirects for the app dial.