From 5d08a678d60eb51d6199325a2f1b2a462e892698 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 19 Apr 2024 11:26:26 +0200 Subject: [PATCH 1/7] WIP --- site/e2e/tests/deployment/workspaceProxies.spec.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 site/e2e/tests/deployment/workspaceProxies.spec.ts diff --git a/site/e2e/tests/deployment/workspaceProxies.spec.ts b/site/e2e/tests/deployment/workspaceProxies.spec.ts new file mode 100644 index 0000000000000..c2e494edc74e2 --- /dev/null +++ b/site/e2e/tests/deployment/workspaceProxies.spec.ts @@ -0,0 +1,14 @@ +import { test } from "@playwright/test"; +import { + setupApiCalls, +} from "../../api"; + +test("workspace proxies configuration", async ({ page }) => { + await setupApiCalls(page); + + await page.goto("/deployment/workspace-proxies", { + waitUntil: "domcontentloaded", + }); + + await page.pause(); +}); From b9285cb078e0a21fdbf00f1b71d2016caa184ddc Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 19 Apr 2024 11:50:52 +0200 Subject: [PATCH 2/7] e2e workspace proxy --- .../tests/deployment/workspaceProxies.spec.ts | 24 ++++++++++++++----- .../WorkspaceProxyPage/WorkspaceProxyRow.tsx | 10 +++++--- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/site/e2e/tests/deployment/workspaceProxies.spec.ts b/site/e2e/tests/deployment/workspaceProxies.spec.ts index c2e494edc74e2..60788e2a56126 100644 --- a/site/e2e/tests/deployment/workspaceProxies.spec.ts +++ b/site/e2e/tests/deployment/workspaceProxies.spec.ts @@ -1,14 +1,26 @@ -import { test } from "@playwright/test"; -import { - setupApiCalls, -} from "../../api"; +import { test, expect } from "@playwright/test"; +import { setupApiCalls } from "../../api"; +import { coderPort } from "../../constants"; +import { requiresEnterpriseLicense } from "../../helpers"; + +test("default proxy is online", async ({ page }) => { + requiresEnterpriseLicense(); -test("workspace proxies configuration", async ({ page }) => { await setupApiCalls(page); await page.goto("/deployment/workspace-proxies", { waitUntil: "domcontentloaded", }); - await page.pause(); + const workspaceProxyPrimary = page.locator( + `table.MuiTable-root tr[data-testid="primary"]`, + ); + + const workspaceProxyName = workspaceProxyPrimary.locator("td.name span"); + const workspaceProxyURL = workspaceProxyPrimary.locator("td.url"); + const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span"); + + await expect(workspaceProxyName).toHaveText("Default"); + await expect(workspaceProxyURL).toHaveText("http://localhost:" + coderPort); + await expect(workspaceProxyStatus).toHaveText("Healthy"); }); diff --git a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx index fbd76ba2be97a..e0807cf7588bc 100644 --- a/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx +++ b/site/src/pages/UserSettingsPage/WorkspaceProxyPage/WorkspaceProxyRow.tsx @@ -40,7 +40,7 @@ export const ProxyRow: FC = ({ proxy, latency }) => { return ( <> - + 0 @@ -60,8 +60,12 @@ export const ProxyRow: FC = ({ proxy, latency }) => { /> - {proxy.path_app_url} - {statusBadge} + + {proxy.path_app_url} + + + {statusBadge} + Date: Fri, 19 Apr 2024 12:43:55 +0200 Subject: [PATCH 3/7] WIP --- .../tests/deployment/workspaceProxies.spec.ts | 32 ++++++++++++++++++- site/src/api/api.ts | 7 ++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/site/e2e/tests/deployment/workspaceProxies.spec.ts b/site/e2e/tests/deployment/workspaceProxies.spec.ts index 60788e2a56126..8a2293119859d 100644 --- a/site/e2e/tests/deployment/workspaceProxies.spec.ts +++ b/site/e2e/tests/deployment/workspaceProxies.spec.ts @@ -1,7 +1,8 @@ import { test, expect } from "@playwright/test"; +import { createWorkspaceProxy } from "api/api"; import { setupApiCalls } from "../../api"; import { coderPort } from "../../constants"; -import { requiresEnterpriseLicense } from "../../helpers"; +import { randomName, requiresEnterpriseLicense } from "../../helpers"; test("default proxy is online", async ({ page }) => { requiresEnterpriseLicense(); @@ -24,3 +25,32 @@ test("default proxy is online", async ({ page }) => { await expect(workspaceProxyURL).toHaveText("http://localhost:" + coderPort); await expect(workspaceProxyStatus).toHaveText("Healthy"); }); + +test("custom proxy is online", async ({ page }) => { + requiresEnterpriseLicense(); + await setupApiCalls(page); + + const proxyName = randomName(); + const proxyResponse = await createWorkspaceProxy({ + name: proxyName, + display_name: "", + icon: "/emojis/1f1e7-1f1f7.png", + }); + expect(proxyResponse.proxy_token).not.toHaveLength(4); + + await page.goto("/deployment/workspace-proxies", { + waitUntil: "domcontentloaded", + }); + + const workspaceProxyPrimary = page.locator(`table.MuiTable-root tr`, { + hasText: proxyName, + }); + + const workspaceProxyName = workspaceProxyPrimary.locator("td.name span"); + //const workspaceProxyURL = workspaceProxyPrimary.locator("td.url"); + const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span"); + + await expect(workspaceProxyName).toHaveText(proxyName); + //await expect(workspaceProxyURL).toHaveText("http://localhost:" + coderPort); + await expect(workspaceProxyStatus).toHaveText("Never seen"); +}); diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 12c2a63b2c014..a243123a310f3 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1270,6 +1270,13 @@ export const getWorkspaceProxies = async (): Promise< return response.data; }; +export const createWorkspaceProxy = async ( + b: TypesGen.CreateWorkspaceProxyRequest, +): Promise => { + const response = await axios.post(`/api/v2/workspaceproxies`, b); + return response.data; +}; + export const getAppearance = async (): Promise => { try { const response = await axios.get(`/api/v2/appearance`); From fb8441a86eb44bfa087150e88cee80b1d535694a Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 19 Apr 2024 12:44:42 +0200 Subject: [PATCH 4/7] fix --- site/e2e/tests/deployment/workspaceProxies.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/e2e/tests/deployment/workspaceProxies.spec.ts b/site/e2e/tests/deployment/workspaceProxies.spec.ts index 8a2293119859d..6485b840a0b07 100644 --- a/site/e2e/tests/deployment/workspaceProxies.spec.ts +++ b/site/e2e/tests/deployment/workspaceProxies.spec.ts @@ -42,13 +42,13 @@ test("custom proxy is online", async ({ page }) => { waitUntil: "domcontentloaded", }); - const workspaceProxyPrimary = page.locator(`table.MuiTable-root tr`, { + const workspaceProxy = page.locator(`table.MuiTable-root tr`, { hasText: proxyName, }); - const workspaceProxyName = workspaceProxyPrimary.locator("td.name span"); + const workspaceProxyName = workspaceProxy.locator("td.name span"); //const workspaceProxyURL = workspaceProxyPrimary.locator("td.url"); - const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span"); + const workspaceProxyStatus = workspaceProxy.locator("td.status span"); await expect(workspaceProxyName).toHaveText(proxyName); //await expect(workspaceProxyURL).toHaveText("http://localhost:" + coderPort); From d78f99ec72dada1f99b8de7850e235fd6a2f68e0 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 19 Apr 2024 14:12:37 +0200 Subject: [PATCH 5/7] start proxy --- site/e2e/constants.ts | 1 + site/e2e/helpers.ts | 2 +- site/e2e/proxy.ts | 41 +++++++++++++ .../tests/deployment/workspaceProxies.spec.ts | 61 +++++++++++++++++-- 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 site/e2e/proxy.ts diff --git a/site/e2e/constants.ts b/site/e2e/constants.ts index 6998968977908..3e1283e5491c4 100644 --- a/site/e2e/constants.ts +++ b/site/e2e/constants.ts @@ -7,6 +7,7 @@ export const coderPort = process.env.CODER_E2E_PORT ? Number(process.env.CODER_E2E_PORT) : 3111; export const prometheusPort = 2114; +export const workspaceProxyPort = 3112; // Use alternate ports in case we're running in a Coder Workspace. export const agentPProfPort = 6061; diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index ca3a507007cf5..26e416cad7158 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -391,7 +391,7 @@ export const stopAgent = async (cp: ChildProcess, goRun: boolean = true) => { await waitUntilUrlIsNotResponding("http://localhost:" + prometheusPort); }; -const waitUntilUrlIsNotResponding = async (url: string) => { +export const waitUntilUrlIsNotResponding = async (url: string) => { const maxRetries = 30; const retryIntervalMs = 1000; let retries = 0; diff --git a/site/e2e/proxy.ts b/site/e2e/proxy.ts new file mode 100644 index 0000000000000..620fcf0a96015 --- /dev/null +++ b/site/e2e/proxy.ts @@ -0,0 +1,41 @@ +import { spawn, type ChildProcess, exec } from "child_process"; +import { coderMain, coderPort, workspaceProxyPort } from "./constants"; +import { waitUntilUrlIsNotResponding } from "./helpers"; + +export const startWorkspaceProxy = async ( + token: string, +): Promise => { + const cp = spawn("go", ["run", coderMain, "wsproxy", "server"], { + env: { + ...process.env, + CODER_PRIMARY_ACCESS_URL: `http://127.0.0.1:${coderPort}`, + CODER_PROXY_SESSION_TOKEN: token, + CODER_HTTP_ADDRESS: `localhost:${workspaceProxyPort}`, + }, + }); + cp.stdout.on("data", (data: Buffer) => { + // eslint-disable-next-line no-console -- Log wsproxy activity + console.log( + `[wsproxy] [stdout] [onData] ${data.toString().replace(/\n$/g, "")}`, + ); + }); + cp.stderr.on("data", (data: Buffer) => { + // eslint-disable-next-line no-console -- Log wsproxy activity + console.log( + `[wsproxy] [stderr] [onData] ${data.toString().replace(/\n$/g, "")}`, + ); + }); + return cp; +}; + +export const stopWorkspaceProxy = async ( + cp: ChildProcess, + goRun: boolean = true, +) => { + exec(goRun ? `pkill -P ${cp.pid}` : `kill ${cp.pid}`, (error) => { + if (error) { + throw new Error(`exec error: ${JSON.stringify(error)}`); + } + }); + await waitUntilUrlIsNotResponding(`http://127.0.0.1:${workspaceProxyPort}`); +}; diff --git a/site/e2e/tests/deployment/workspaceProxies.spec.ts b/site/e2e/tests/deployment/workspaceProxies.spec.ts index 6485b840a0b07..826e787d08811 100644 --- a/site/e2e/tests/deployment/workspaceProxies.spec.ts +++ b/site/e2e/tests/deployment/workspaceProxies.spec.ts @@ -1,8 +1,9 @@ -import { test, expect } from "@playwright/test"; +import { test, expect, type Page } from "@playwright/test"; import { createWorkspaceProxy } from "api/api"; import { setupApiCalls } from "../../api"; -import { coderPort } from "../../constants"; +import { coderPort, workspaceProxyPort } from "../../constants"; import { randomName, requiresEnterpriseLicense } from "../../helpers"; +import { startWorkspaceProxy, stopWorkspaceProxy } from "../../proxy"; test("default proxy is online", async ({ page }) => { requiresEnterpriseLicense(); @@ -31,13 +32,21 @@ test("custom proxy is online", async ({ page }) => { await setupApiCalls(page); const proxyName = randomName(); + + // Register workspace proxy const proxyResponse = await createWorkspaceProxy({ name: proxyName, display_name: "", icon: "/emojis/1f1e7-1f1f7.png", }); - expect(proxyResponse.proxy_token).not.toHaveLength(4); + expect(proxyResponse.proxy_token).toBeDefined(); + + // Start "wsproxy server" + const proxyServer = await startWorkspaceProxy(proxyResponse.proxy_token); + await waitUntilWorkspaceProxyIsHealthy(page, proxyName); + + // Verify if proxy is healthy await page.goto("/deployment/workspace-proxies", { waitUntil: "domcontentloaded", }); @@ -47,10 +56,50 @@ test("custom proxy is online", async ({ page }) => { }); const workspaceProxyName = workspaceProxy.locator("td.name span"); - //const workspaceProxyURL = workspaceProxyPrimary.locator("td.url"); + const workspaceProxyURL = workspaceProxy.locator("td.url"); const workspaceProxyStatus = workspaceProxy.locator("td.status span"); await expect(workspaceProxyName).toHaveText(proxyName); - //await expect(workspaceProxyURL).toHaveText("http://localhost:" + coderPort); - await expect(workspaceProxyStatus).toHaveText("Never seen"); + await expect(workspaceProxyURL).toHaveText( + `http://127.0.0.1:${workspaceProxyPort}`, + ); + await expect(workspaceProxyStatus).toHaveText("Healthy"); + + await stopWorkspaceProxy(proxyServer); }); + +const waitUntilWorkspaceProxyIsHealthy = async ( + page: Page, + proxyName: string, +) => { + await page.goto("/deployment/workspace-proxies", { + waitUntil: "domcontentloaded", + }); + + const maxRetries = 30; + const retryIntervalMs = 1000; + let retries = 0; + while (retries < maxRetries) { + await page.reload(); + + const workspaceProxy = page.locator(`table.MuiTable-root tr`, { + hasText: proxyName, + }); + const workspaceProxyStatus = workspaceProxy.locator("td.status span"); + + try { + await expect(workspaceProxyStatus).toHaveText("Healthy", { + timeout: 1_000, + }); + return; // healthy! + } catch { + retries++; + await new Promise((resolve) => setTimeout(resolve, retryIntervalMs)); + } + } + throw new Error( + `Workspace proxy "${proxyName}" is unhealthy after ${ + maxRetries * retryIntervalMs + }ms`, + ); +}; From 6fdb908ba6cf70f15450ff7ef91d6922013c8de8 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 19 Apr 2024 14:18:48 +0200 Subject: [PATCH 6/7] comments --- site/e2e/tests/deployment/workspaceProxies.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/e2e/tests/deployment/workspaceProxies.spec.ts b/site/e2e/tests/deployment/workspaceProxies.spec.ts index 826e787d08811..5f67bda7d7ad4 100644 --- a/site/e2e/tests/deployment/workspaceProxies.spec.ts +++ b/site/e2e/tests/deployment/workspaceProxies.spec.ts @@ -7,13 +7,13 @@ import { startWorkspaceProxy, stopWorkspaceProxy } from "../../proxy"; test("default proxy is online", async ({ page }) => { requiresEnterpriseLicense(); - await setupApiCalls(page); await page.goto("/deployment/workspace-proxies", { waitUntil: "domcontentloaded", }); + // Verify if the default proxy is healthy const workspaceProxyPrimary = page.locator( `table.MuiTable-root tr[data-testid="primary"]`, ); @@ -43,10 +43,9 @@ test("custom proxy is online", async ({ page }) => { // Start "wsproxy server" const proxyServer = await startWorkspaceProxy(proxyResponse.proxy_token); - await waitUntilWorkspaceProxyIsHealthy(page, proxyName); - // Verify if proxy is healthy + // Verify if custom proxy is healthy await page.goto("/deployment/workspace-proxies", { waitUntil: "domcontentloaded", }); @@ -65,6 +64,7 @@ test("custom proxy is online", async ({ page }) => { ); await expect(workspaceProxyStatus).toHaveText("Healthy"); + // Tear down the proxy await stopWorkspaceProxy(proxyServer); }); From 1af97d6830235e4d9bfb80f1778326140710acb7 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Fri, 19 Apr 2024 14:25:47 +0200 Subject: [PATCH 7/7] fix: logo --- site/e2e/tests/deployment/appearance.spec.ts | 2 +- site/src/pages/LoginPage/LoginPageView.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/site/e2e/tests/deployment/appearance.spec.ts b/site/e2e/tests/deployment/appearance.spec.ts index 0fec1a7d75597..14aeafe75063b 100644 --- a/site/e2e/tests/deployment/appearance.spec.ts +++ b/site/e2e/tests/deployment/appearance.spec.ts @@ -52,7 +52,7 @@ test("set application logo", async ({ page }) => { await incognitoPage.goto("/", { waitUntil: "domcontentloaded" }); // Verify banner - const logo = incognitoPage.locator("img"); + const logo = incognitoPage.locator("img.application-logo"); await expect(logo).toHaveAttribute("src", imageLink); // Shut down browser diff --git a/site/src/pages/LoginPage/LoginPageView.tsx b/site/src/pages/LoginPage/LoginPageView.tsx index b3039e5ad9da1..e62e8be0d834e 100644 --- a/site/src/pages/LoginPage/LoginPageView.tsx +++ b/site/src/pages/LoginPage/LoginPageView.tsx @@ -41,6 +41,7 @@ export const LoginPageView: FC = ({ css={{ maxWidth: "200px", }} + className="application-logo" /> ) : (