From 9151a1f8401e41b5e033b7b7194c4a6e4ce4da25 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 27 Jul 2023 04:41:10 +0000 Subject: [PATCH 1/3] chore: add e2e test for backwards ssh compatibility --- site/e2e/helpers.ts | 69 +++++++++++++++++++++++++--- site/e2e/tests/outdatedAgent.spec.ts | 65 ++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 site/e2e/tests/outdatedAgent.spec.ts diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index f9d37e3190eee..78195bae1fba7 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -1,4 +1,4 @@ -import { expect, Page } from "@playwright/test" +import { expect, Page, TestInfo } from "@playwright/test" import { spawn } from "child_process" import { randomUUID } from "crypto" import path from "path" @@ -71,7 +71,55 @@ export const startAgent = async (page: Page, token: string): Promise => { "coder", "main.go", ) - const cp = spawn("go", ["run", coderMain, "agent", "--no-reap"], { + return startAgentWithCommand(page, token, "go", "run", coderMain) +} + +export const downloadCoderVersion = async (testInfo: TestInfo, version: string): Promise => { + if (version.startsWith("v")) { + version = version.slice(1) + } + + const binaryName = "coder-e2e-" + version + const tempDir = "/tmp" + // The install script adds `./bin` automatically to the path :shrug: + const binaryPath = path.join(tempDir, "bin", binaryName) + + const exists = await new Promise((resolve) => { + const cp = spawn(binaryPath, ["version"]) + cp.on("close", (code) => { + resolve(code === 0) + }) + cp.on("error", () => resolve(false)) + }) + if (exists) { + return binaryPath + } + + await new Promise((resolve, reject) => { + const cp = spawn("sh", ["-c", [ + "curl", "-L", "https://coder.com/install.sh", + "|", + "sh", "-s", "--", + "--version", version, + "--method", "standalone", + "--prefix", tempDir, + "--binary-name", binaryName, + ].join(" ")]) + cp.stderr.on("data", (data) => testInfo.stderr.push(data)) + cp.stdout.on("data", (data) => testInfo.stdout.push(data)) + cp.on("close", (code) => { + if (code === 0) { + resolve() + } else { + reject(new Error("curl failed with code " + code)) + } + }) + }) + return binaryPath +} + +export const startAgentWithCommand = async (page: Page, token: string, command: string, ...args: string[]): Promise => { + const cp = spawn(command, [...args, "agent", "--no-reap"], { env: { ...process.env, CODER_AGENT_URL: "http://localhost:" + port, @@ -93,10 +141,10 @@ export const startAgent = async (page: Page, token: string): Promise => { // Allows users to more easily define properties they want for agents and resources! type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] - ? RecursivePartial[] - : T[P] extends object | undefined - ? RecursivePartial - : T[P] + ? RecursivePartial[] + : T[P] extends object | undefined + ? RecursivePartial + : T[P] } interface EchoProvisionerResponses { @@ -259,3 +307,12 @@ export const createServer = async ( await new Promise((r) => e.listen(port, r)) return e } + +export const findSessionToken = async (page: Page): Promise => { + const cookies = await page.context().cookies() + const sessionCookie = cookies.find((c) => c.name === "coder_session_token") + if (!sessionCookie) { + throw new Error("session token not found") + } + return sessionCookie.value +} diff --git a/site/e2e/tests/outdatedAgent.spec.ts b/site/e2e/tests/outdatedAgent.spec.ts new file mode 100644 index 0000000000000..398ea9536a508 --- /dev/null +++ b/site/e2e/tests/outdatedAgent.spec.ts @@ -0,0 +1,65 @@ +import { test } from "@playwright/test" +import { createTemplate, createWorkspace, downloadCoderVersion, findSessionToken, startAgentWithCommand } from "../helpers" +import { randomUUID } from "crypto" +import { spawn } from "child_process" +import path from "path" + +test("create workspace with an outdated agent", async ({ page }, testInfo) => { + const token = randomUUID() + const template = await createTemplate(page, { + apply: [ + { + complete: { + resources: [ + { + agents: [ + { + token, + }, + ], + }, + ], + }, + }, + ], + }) + const workspace = await createWorkspace(page, template) + const binaryPath = await downloadCoderVersion(testInfo, "v0.24.0") + await startAgentWithCommand(page, token, binaryPath) + const sessionToken = await findSessionToken(page) + const coderMain = path.join( + __dirname, + "..", + "..", + "..", + "enterprise", + "cmd", + "coder", + "main.go", + ) + await new Promise((resolve, reject) => { + const cp = spawn("ssh", [ + "-o", "StrictHostKeyChecking=no", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "ProxyCommand=/usr/local/go/bin/go run "+coderMain+" ssh --stdio " + workspace, + "localhost", + "exit", + "0", + ], { + env: { + ...process.env, + CODER_SESSION_TOKEN: sessionToken, + CODER_URL: "http://localhost:3000", + }, + }) + cp.stderr.on("data", (data) => console.log(data.toString())) + cp.stdout.on("data", (data) => console.log(data.toString())) + cp.on("close", (code) => { + if (code === 0) { + resolve() + } else { + reject(new Error("ssh failed with code " + code)) + } + }) + }) +}) From f2db72a7a957d52b8152f97a4a50acf25bc8da2d Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 27 Jul 2023 15:11:12 +0000 Subject: [PATCH 2/3] Use the SSH client directly --- site/e2e/helpers.ts | 77 ++++++++++++++++++++++------ site/e2e/tests/outdatedAgent.spec.ts | 55 +++++++------------- site/package.json | 2 + site/yarn.lock | 57 +++++++++++++++++++- 4 files changed, 138 insertions(+), 53 deletions(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 78195bae1fba7..097a445aa1233 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -1,4 +1,4 @@ -import { expect, Page, TestInfo } from "@playwright/test" +import { expect, Page } from "@playwright/test" import { spawn } from "child_process" import { randomUUID } from "crypto" import path from "path" @@ -15,6 +15,8 @@ import { Resource, } from "./provisionerGenerated" import { port } from "./playwright.config" +import * as ssh from "ssh2" +import { Duplex } from "stream" // createWorkspace creates a workspace for a template. // It does not wait for it to be running, but it does navigate to the page. @@ -59,22 +61,53 @@ export const createTemplate = async ( return name } +// sshIntoWorkspace spawns a Coder SSH process and a client connected to it. +export const sshIntoWorkspace = async (page: Page, workspace: string): Promise => { + const sessionToken = await findSessionToken(page) + return new Promise((resolve, reject) => { + const cp = spawn("go", ["run", coderMainPath(), "ssh", "--stdio", workspace], { + env: { + ...process.env, + CODER_SESSION_TOKEN: sessionToken, + CODER_URL: "http://localhost:3000", + }, + }) + cp.on("error", (err) => reject(err)) + const proxyStream = new Duplex({ + read: (size) => { + return cp.stdout.read(Math.min(size, cp.stdout.readableLength)) + }, + write: cp.stdin.write.bind(cp.stdin), + }) + // eslint-disable-next-line no-console -- Helpful for debugging + cp.stderr.on("data", (data) => console.log(data.toString())) + cp.stdout.on("readable", (...args) => { + proxyStream.emit('readable', ...args); + if (cp.stdout.readableLength > 0) { + proxyStream.emit("data", cp.stdout.read()); + } + }); + const client = new ssh.Client() + client.connect({ + sock: proxyStream, + username: "coder", + }) + client.on("error", (err) => reject(err)) + client.on("ready", () => { + resolve(client) + }) + }) +} + // startAgent runs the coder agent with the provided token. // It awaits the agent to be ready before returning. export const startAgent = async (page: Page, token: string): Promise => { - const coderMain = path.join( - __dirname, - "..", - "..", - "enterprise", - "cmd", - "coder", - "main.go", - ) - return startAgentWithCommand(page, token, "go", "run", coderMain) + return startAgentWithCommand(page, token, "go", "run", coderMainPath()) } -export const downloadCoderVersion = async (testInfo: TestInfo, version: string): Promise => { +// downloadCoderVersion downloads the version provided into a temporary dir and +// caches it so subsequent calls are fast. +export const downloadCoderVersion = async (version: string): Promise => { if (version.startsWith("v")) { version = version.slice(1) } @@ -95,6 +128,8 @@ export const downloadCoderVersion = async (testInfo: TestInfo, version: string): return binaryPath } + // Runs our public install script using our options to + // install the binary! await new Promise((resolve, reject) => { const cp = spawn("sh", ["-c", [ "curl", "-L", "https://coder.com/install.sh", @@ -105,8 +140,8 @@ export const downloadCoderVersion = async (testInfo: TestInfo, version: string): "--prefix", tempDir, "--binary-name", binaryName, ].join(" ")]) - cp.stderr.on("data", (data) => testInfo.stderr.push(data)) - cp.stdout.on("data", (data) => testInfo.stdout.push(data)) + // eslint-disable-next-line no-console -- Needed for debugging + cp.stderr.on("data", (data) => console.log(data.toString())) cp.on("close", (code) => { if (code === 0) { resolve() @@ -138,6 +173,18 @@ export const startAgentWithCommand = async (page: Page, token: string, command: } } +const coderMainPath = (): string => { + return path.join( + __dirname, + "..", + "..", + "enterprise", + "cmd", + "coder", + "main.go", + ) +} + // Allows users to more easily define properties they want for agents and resources! type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] @@ -308,7 +355,7 @@ export const createServer = async ( return e } -export const findSessionToken = async (page: Page): Promise => { +const findSessionToken = async (page: Page): Promise => { const cookies = await page.context().cookies() const sessionCookie = cookies.find((c) => c.name === "coder_session_token") if (!sessionCookie) { diff --git a/site/e2e/tests/outdatedAgent.spec.ts b/site/e2e/tests/outdatedAgent.spec.ts index 398ea9536a508..0588939d4c3f3 100644 --- a/site/e2e/tests/outdatedAgent.spec.ts +++ b/site/e2e/tests/outdatedAgent.spec.ts @@ -1,10 +1,10 @@ import { test } from "@playwright/test" -import { createTemplate, createWorkspace, downloadCoderVersion, findSessionToken, startAgentWithCommand } from "../helpers" import { randomUUID } from "crypto" -import { spawn } from "child_process" -import path from "path" +import { createTemplate, createWorkspace, downloadCoderVersion, sshIntoWorkspace, startAgentWithCommand } from "../helpers" -test("create workspace with an outdated agent", async ({ page }, testInfo) => { +const agentVersion = "v0.14.0" + +test("ssh with agent " + agentVersion, async ({ page }) => { const token = randomUUID() const template = await createTemplate(page, { apply: [ @@ -24,42 +24,23 @@ test("create workspace with an outdated agent", async ({ page }, testInfo) => { ], }) const workspace = await createWorkspace(page, template) - const binaryPath = await downloadCoderVersion(testInfo, "v0.24.0") + const binaryPath = await downloadCoderVersion(agentVersion) await startAgentWithCommand(page, token, binaryPath) - const sessionToken = await findSessionToken(page) - const coderMain = path.join( - __dirname, - "..", - "..", - "..", - "enterprise", - "cmd", - "coder", - "main.go", - ) + + const client = await sshIntoWorkspace(page, workspace) await new Promise((resolve, reject) => { - const cp = spawn("ssh", [ - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "ProxyCommand=/usr/local/go/bin/go run "+coderMain+" ssh --stdio " + workspace, - "localhost", - "exit", - "0", - ], { - env: { - ...process.env, - CODER_SESSION_TOKEN: sessionToken, - CODER_URL: "http://localhost:3000", - }, - }) - cp.stderr.on("data", (data) => console.log(data.toString())) - cp.stdout.on("data", (data) => console.log(data.toString())) - cp.on("close", (code) => { - if (code === 0) { - resolve() - } else { - reject(new Error("ssh failed with code " + code)) + // We just exec a command to be certain the agent is running! + client.exec("exit 0", (err, stream) => { + if (err) { + return reject(err) } + stream.on("exit", (code) => { + if (code !== 0) { + return reject(new Error(`Command exited with code ${code}`)) + } + client.end(); + resolve() + }); }) }) }) diff --git a/site/package.json b/site/package.json index 6a8e6ae153658..eca5b618ee862 100644 --- a/site/package.json +++ b/site/package.json @@ -126,6 +126,7 @@ "@types/react-syntax-highlighter": "15.5.5", "@types/react-virtualized-auto-sizer": "1.0.1", "@types/react-window": "1.8.5", + "@types/ssh2": "1.11.13", "@types/ua-parser-js": "0.7.36", "@types/uuid": "9.0.2", "@typescript-eslint/eslint-plugin": "5.62.0", @@ -153,6 +154,7 @@ "msw": "1.2.2", "prettier": "3.0.0", "resize-observer": "1.0.4", + "ssh2": "1.14.0", "storybook": "7.1.0", "storybook-addon-react-router-v6": "1.0.2", "storybook-react-context": "0.6.0", diff --git a/site/yarn.lock b/site/yarn.lock index bbbe6d3984c13..120bb9a46df5a 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -3751,6 +3751,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.23.tgz#b6e934fe427eb7081d0015aad070acb3373c3c90" integrity sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g== +"@types/node@^18.11.18": + version "18.17.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.1.tgz#84c32903bf3a09f7878c391d31ff08f6fe7d8335" + integrity sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw== + "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" @@ -3905,6 +3910,13 @@ dependencies: "@types/node" "*" +"@types/ssh2@1.11.13": + version "1.11.13" + resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-1.11.13.tgz#e6224da936abec0541bf26aa826b1cc37ea70d69" + integrity sha512-08WbG68HvQ2YVi74n2iSUnYHYpUdFc/s2IsI0BHBdJwaqYJpWlVv9elL0tYShTv60yr0ObdxJR5NrCRiGJ/0CQ== + dependencies: + "@types/node" "^18.11.18" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -4374,6 +4386,13 @@ array.prototype.flatmap@^1.3.0, array.prototype.flatmap@^1.3.1: es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" +asn1@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + assert@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" @@ -4571,6 +4590,13 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bcrypt-pbkdf@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + better-opn@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-3.0.2.tgz#f96f35deaaf8f34144a4102651babcf00d1d8817" @@ -4701,6 +4727,11 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buildcheck@~0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238" + integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A== + builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -5177,6 +5208,14 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +cpu-features@~0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.8.tgz#a2d464b023b8ad09004c8cdca23b33f192f63546" + integrity sha512-BbHBvtYhUhksqTjr6bhNOjGgMnhwhGTQmOoZGD+K7BCaQDCuZl/Ve1ZxUSMRwVC4D/rkCPQ2MAIeYzrWyK7eEg== + dependencies: + buildcheck "~0.0.6" + nan "^2.17.0" + create-jest-runner@^0.11.2: version "0.11.2" resolved "https://registry.yarnpkg.com/create-jest-runner/-/create-jest-runner-0.11.2.tgz#4b4f62ccef1e4de12e80f81c2cf8211fa392a962" @@ -10511,7 +10550,7 @@ safe-regex@^2.1.1: dependencies: regexp-tree "~0.1.1" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -10775,6 +10814,17 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +ssh2@1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.14.0.tgz#8f68440e1b768b66942c9e4e4620b2725b3555bb" + integrity sha512-AqzD1UCqit8tbOKoj6ztDDi1ffJZ2rV2SwlgrVVrHPkV5vWqGJOVp5pmtj18PunkPJAuKQsnInyKV+/Nb2bUnA== + dependencies: + asn1 "^0.2.6" + bcrypt-pbkdf "^1.0.2" + optionalDependencies: + cpu-features "~0.0.8" + nan "^2.17.0" + stack-generator@^2.0.5: version "2.0.10" resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d" @@ -11362,6 +11412,11 @@ tween-functions@^1.2.0: resolved "https://registry.yarnpkg.com/tween-functions/-/tween-functions-1.2.0.tgz#1ae3a50e7c60bb3def774eac707acbca73bbc3ff" integrity sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA== +tweetnacl@^0.14.3: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" From 8cff979b815967d08cfda5c51cfd48d1ce5e8646 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Thu, 27 Jul 2023 15:20:10 +0000 Subject: [PATCH 3/3] fmt --- site/e2e/helpers.ts | 75 ++++++++++++++++++---------- site/e2e/tests/outdatedAgent.spec.ts | 12 +++-- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index 097a445aa1233..3194192e6730c 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -62,16 +62,23 @@ export const createTemplate = async ( } // sshIntoWorkspace spawns a Coder SSH process and a client connected to it. -export const sshIntoWorkspace = async (page: Page, workspace: string): Promise => { +export const sshIntoWorkspace = async ( + page: Page, + workspace: string, +): Promise => { const sessionToken = await findSessionToken(page) return new Promise((resolve, reject) => { - const cp = spawn("go", ["run", coderMainPath(), "ssh", "--stdio", workspace], { - env: { - ...process.env, - CODER_SESSION_TOKEN: sessionToken, - CODER_URL: "http://localhost:3000", + const cp = spawn( + "go", + ["run", coderMainPath(), "ssh", "--stdio", workspace], + { + env: { + ...process.env, + CODER_SESSION_TOKEN: sessionToken, + CODER_URL: "http://localhost:3000", + }, }, - }) + ) cp.on("error", (err) => reject(err)) const proxyStream = new Duplex({ read: (size) => { @@ -82,11 +89,11 @@ export const sshIntoWorkspace = async (page: Page, workspace: string): Promise console.log(data.toString())) cp.stdout.on("readable", (...args) => { - proxyStream.emit('readable', ...args); + proxyStream.emit("readable", ...args) if (cp.stdout.readableLength > 0) { - proxyStream.emit("data", cp.stdout.read()); + proxyStream.emit("data", cp.stdout.read()) } - }); + }) const client = new ssh.Client() client.connect({ sock: proxyStream, @@ -107,7 +114,9 @@ export const startAgent = async (page: Page, token: string): Promise => { // downloadCoderVersion downloads the version provided into a temporary dir and // caches it so subsequent calls are fast. -export const downloadCoderVersion = async (version: string): Promise => { +export const downloadCoderVersion = async ( + version: string, +): Promise => { if (version.startsWith("v")) { version = version.slice(1) } @@ -131,15 +140,26 @@ export const downloadCoderVersion = async (version: string): Promise => // Runs our public install script using our options to // install the binary! await new Promise((resolve, reject) => { - const cp = spawn("sh", ["-c", [ - "curl", "-L", "https://coder.com/install.sh", - "|", - "sh", "-s", "--", - "--version", version, - "--method", "standalone", - "--prefix", tempDir, - "--binary-name", binaryName, - ].join(" ")]) + const cp = spawn("sh", [ + "-c", + [ + "curl", + "-L", + "https://coder.com/install.sh", + "|", + "sh", + "-s", + "--", + "--version", + version, + "--method", + "standalone", + "--prefix", + tempDir, + "--binary-name", + binaryName, + ].join(" "), + ]) // eslint-disable-next-line no-console -- Needed for debugging cp.stderr.on("data", (data) => console.log(data.toString())) cp.on("close", (code) => { @@ -153,7 +173,12 @@ export const downloadCoderVersion = async (version: string): Promise => return binaryPath } -export const startAgentWithCommand = async (page: Page, token: string, command: string, ...args: string[]): Promise => { +export const startAgentWithCommand = async ( + page: Page, + token: string, + command: string, + ...args: string[] +): Promise => { const cp = spawn(command, [...args, "agent", "--no-reap"], { env: { ...process.env, @@ -188,10 +213,10 @@ const coderMainPath = (): string => { // Allows users to more easily define properties they want for agents and resources! type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] - ? RecursivePartial[] - : T[P] extends object | undefined - ? RecursivePartial - : T[P] + ? RecursivePartial[] + : T[P] extends object | undefined + ? RecursivePartial + : T[P] } interface EchoProvisionerResponses { diff --git a/site/e2e/tests/outdatedAgent.spec.ts b/site/e2e/tests/outdatedAgent.spec.ts index 0588939d4c3f3..2b88ea71110df 100644 --- a/site/e2e/tests/outdatedAgent.spec.ts +++ b/site/e2e/tests/outdatedAgent.spec.ts @@ -1,6 +1,12 @@ import { test } from "@playwright/test" import { randomUUID } from "crypto" -import { createTemplate, createWorkspace, downloadCoderVersion, sshIntoWorkspace, startAgentWithCommand } from "../helpers" +import { + createTemplate, + createWorkspace, + downloadCoderVersion, + sshIntoWorkspace, + startAgentWithCommand, +} from "../helpers" const agentVersion = "v0.14.0" @@ -38,9 +44,9 @@ test("ssh with agent " + agentVersion, async ({ page }) => { if (code !== 0) { return reject(new Error(`Command exited with code ${code}`)) } - client.end(); + client.end() resolve() - }); + }) }) }) }) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy