Skip to content

Commit 9151a1f

Browse files
committed
chore: add e2e test for backwards ssh compatibility
1 parent 74c4553 commit 9151a1f

File tree

2 files changed

+128
-6
lines changed

2 files changed

+128
-6
lines changed

site/e2e/helpers.ts

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, Page } from "@playwright/test"
1+
import { expect, Page, TestInfo } from "@playwright/test"
22
import { spawn } from "child_process"
33
import { randomUUID } from "crypto"
44
import path from "path"
@@ -71,7 +71,55 @@ export const startAgent = async (page: Page, token: string): Promise<void> => {
7171
"coder",
7272
"main.go",
7373
)
74-
const cp = spawn("go", ["run", coderMain, "agent", "--no-reap"], {
74+
return startAgentWithCommand(page, token, "go", "run", coderMain)
75+
}
76+
77+
export const downloadCoderVersion = async (testInfo: TestInfo, version: string): Promise<string> => {
78+
if (version.startsWith("v")) {
79+
version = version.slice(1)
80+
}
81+
82+
const binaryName = "coder-e2e-" + version
83+
const tempDir = "/tmp"
84+
// The install script adds `./bin` automatically to the path :shrug:
85+
const binaryPath = path.join(tempDir, "bin", binaryName)
86+
87+
const exists = await new Promise<boolean>((resolve) => {
88+
const cp = spawn(binaryPath, ["version"])
89+
cp.on("close", (code) => {
90+
resolve(code === 0)
91+
})
92+
cp.on("error", () => resolve(false))
93+
})
94+
if (exists) {
95+
return binaryPath
96+
}
97+
98+
await new Promise<void>((resolve, reject) => {
99+
const cp = spawn("sh", ["-c", [
100+
"curl", "-L", "https://coder.com/install.sh",
101+
"|",
102+
"sh", "-s", "--",
103+
"--version", version,
104+
"--method", "standalone",
105+
"--prefix", tempDir,
106+
"--binary-name", binaryName,
107+
].join(" ")])
108+
cp.stderr.on("data", (data) => testInfo.stderr.push(data))
109+
cp.stdout.on("data", (data) => testInfo.stdout.push(data))
110+
cp.on("close", (code) => {
111+
if (code === 0) {
112+
resolve()
113+
} else {
114+
reject(new Error("curl failed with code " + code))
115+
}
116+
})
117+
})
118+
return binaryPath
119+
}
120+
121+
export const startAgentWithCommand = async (page: Page, token: string, command: string, ...args: string[]): Promise<void> => {
122+
const cp = spawn(command, [...args, "agent", "--no-reap"], {
75123
env: {
76124
...process.env,
77125
CODER_AGENT_URL: "http://localhost:" + port,
@@ -93,10 +141,10 @@ export const startAgent = async (page: Page, token: string): Promise<void> => {
93141
// Allows users to more easily define properties they want for agents and resources!
94142
type RecursivePartial<T> = {
95143
[P in keyof T]?: T[P] extends (infer U)[]
96-
? RecursivePartial<U>[]
97-
: T[P] extends object | undefined
98-
? RecursivePartial<T[P]>
99-
: T[P]
144+
? RecursivePartial<U>[]
145+
: T[P] extends object | undefined
146+
? RecursivePartial<T[P]>
147+
: T[P]
100148
}
101149

102150
interface EchoProvisionerResponses {
@@ -259,3 +307,12 @@ export const createServer = async (
259307
await new Promise<void>((r) => e.listen(port, r))
260308
return e
261309
}
310+
311+
export const findSessionToken = async (page: Page): Promise<string> => {
312+
const cookies = await page.context().cookies()
313+
const sessionCookie = cookies.find((c) => c.name === "coder_session_token")
314+
if (!sessionCookie) {
315+
throw new Error("session token not found")
316+
}
317+
return sessionCookie.value
318+
}

site/e2e/tests/outdatedAgent.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { test } from "@playwright/test"
2+
import { createTemplate, createWorkspace, downloadCoderVersion, findSessionToken, startAgentWithCommand } from "../helpers"
3+
import { randomUUID } from "crypto"
4+
import { spawn } from "child_process"
5+
import path from "path"
6+
7+
test("create workspace with an outdated agent", async ({ page }, testInfo) => {
8+
const token = randomUUID()
9+
const template = await createTemplate(page, {
10+
apply: [
11+
{
12+
complete: {
13+
resources: [
14+
{
15+
agents: [
16+
{
17+
token,
18+
},
19+
],
20+
},
21+
],
22+
},
23+
},
24+
],
25+
})
26+
const workspace = await createWorkspace(page, template)
27+
const binaryPath = await downloadCoderVersion(testInfo, "v0.24.0")
28+
await startAgentWithCommand(page, token, binaryPath)
29+
const sessionToken = await findSessionToken(page)
30+
const coderMain = path.join(
31+
__dirname,
32+
"..",
33+
"..",
34+
"..",
35+
"enterprise",
36+
"cmd",
37+
"coder",
38+
"main.go",
39+
)
40+
await new Promise<void>((resolve, reject) => {
41+
const cp = spawn("ssh", [
42+
"-o", "StrictHostKeyChecking=no",
43+
"-o", "UserKnownHostsFile=/dev/null",
44+
"-o", "ProxyCommand=/usr/local/go/bin/go run "+coderMain+" ssh --stdio " + workspace,
45+
"localhost",
46+
"exit",
47+
"0",
48+
], {
49+
env: {
50+
...process.env,
51+
CODER_SESSION_TOKEN: sessionToken,
52+
CODER_URL: "http://localhost:3000",
53+
},
54+
})
55+
cp.stderr.on("data", (data) => console.log(data.toString()))
56+
cp.stdout.on("data", (data) => console.log(data.toString()))
57+
cp.on("close", (code) => {
58+
if (code === 0) {
59+
resolve()
60+
} else {
61+
reject(new Error("ssh failed with code " + code))
62+
}
63+
})
64+
})
65+
})

0 commit comments

Comments
 (0)