-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This will be called before requests and added to the SSH config.
- Loading branch information
1 parent
697b30e
commit 95bd8c2
Showing
7 changed files
with
149 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import * as os from "os" | ||
import { it, expect } from "vitest" | ||
import { getHeaders } from "./headers" | ||
|
||
const logger = { | ||
writeToCoderOutputChannel() { | ||
// no-op | ||
}, | ||
} | ||
|
||
it("should return undefined", async () => { | ||
await expect(getHeaders(undefined, undefined, logger)).resolves.toStrictEqual({}) | ||
await expect(getHeaders("foo", undefined, logger)).resolves.toStrictEqual({}) | ||
await expect(getHeaders(undefined, "foo", logger)).resolves.toStrictEqual({}) | ||
}) | ||
|
||
it("should return headers", async () => { | ||
await expect(getHeaders("foo", "printf foo=bar'\n'baz=qux", logger)).resolves.toStrictEqual({ | ||
foo: "bar", | ||
baz: "qux", | ||
}) | ||
await expect(getHeaders("foo", "printf foo=bar'\r\n'baz=qux", logger)).resolves.toStrictEqual({ | ||
foo: "bar", | ||
baz: "qux", | ||
}) | ||
await expect(getHeaders("foo", "printf foo=bar'\r\n'", logger)).resolves.toStrictEqual({ foo: "bar" }) | ||
await expect(getHeaders("foo", "printf foo=bar", logger)).resolves.toStrictEqual({ foo: "bar" }) | ||
await expect(getHeaders("foo", "printf foo=bar=", logger)).resolves.toStrictEqual({ foo: "bar=" }) | ||
await expect(getHeaders("foo", "printf foo=bar=baz", logger)).resolves.toStrictEqual({ foo: "bar=baz" }) | ||
await expect(getHeaders("foo", "printf foo=", logger)).resolves.toStrictEqual({ foo: "" }) | ||
}) | ||
|
||
it("should error on malformed or empty lines", async () => { | ||
await expect(getHeaders("foo", "printf foo=bar'\r\n\r\n'", logger)).rejects.toMatch(/Malformed/) | ||
await expect(getHeaders("foo", "printf '\r\n'foo=bar", logger)).rejects.toMatch(/Malformed/) | ||
await expect(getHeaders("foo", "printf =foo", logger)).rejects.toMatch(/Malformed/) | ||
await expect(getHeaders("foo", "printf foo", logger)).rejects.toMatch(/Malformed/) | ||
await expect(getHeaders("foo", "printf ''", logger)).rejects.toMatch(/Malformed/) | ||
}) | ||
|
||
it("should have access to environment variables", async () => { | ||
const coderUrl = "dev.coder.com" | ||
await expect( | ||
getHeaders(coderUrl, os.platform() === "win32" ? "printf url=%CODER_URL" : "printf url=$CODER_URL", logger), | ||
).resolves.toStrictEqual({ url: coderUrl }) | ||
}) | ||
|
||
it("should error on non-zero exit", async () => { | ||
await expect(getHeaders("foo", "exit 10", logger)).rejects.toMatch(/exited unexpectedly with code 10/) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import * as cp from "child_process" | ||
import * as util from "util" | ||
|
||
export interface Logger { | ||
writeToCoderOutputChannel(message: string): void | ||
} | ||
|
||
interface ExecException { | ||
code?: number | ||
stderr?: string | ||
stdout?: string | ||
} | ||
|
||
function isExecException(err: unknown): err is ExecException { | ||
return typeof (err as ExecException).code !== "undefined" | ||
} | ||
|
||
// TODO: getHeaders might make more sense to directly implement on Storage | ||
// but it is difficult to test Storage right now since we use vitest instead of | ||
// the standard extension testing framework which would give us access to vscode | ||
// APIs. We should revert the testing framework then consider moving this. | ||
|
||
// getHeaders executes the header command and parses the headers from stdout. | ||
// Both stdout and stderr are logged on error but stderr is otherwise ignored. | ||
// Throws an error if the process exits with non-zero or the JSON is invalid. | ||
// Returns undefined if there is no header command set. No effort is made to | ||
// validate the JSON other than making sure it can be parsed. | ||
export async function getHeaders( | ||
url: string | undefined, | ||
command: string | undefined, | ||
logger: Logger, | ||
): Promise<Record<string, string>> { | ||
const headers: Record<string, string> = {} | ||
if (typeof url === "string" && url.trim().length > 0 && typeof command === "string" && command.trim().length > 0) { | ||
let result: { stdout: string; stderr: string } | ||
try { | ||
result = await util.promisify(cp.exec)(command, { | ||
env: { | ||
...process.env, | ||
CODER_URL: url, | ||
}, | ||
}) | ||
} catch (error) { | ||
if (isExecException(error)) { | ||
logger.writeToCoderOutputChannel(`Header command exited unexpectedly with code ${error.code}`) | ||
logger.writeToCoderOutputChannel(`stdout: ${error.stdout}`) | ||
logger.writeToCoderOutputChannel(`stderr: ${error.stderr}`) | ||
throw new Error(`Header command exited unexpectedly with code ${error.code}`) | ||
} | ||
throw new Error(`Header command exited unexpectedly: ${error}`) | ||
} | ||
// This should imitate or be a subset of the Coder CLI's behavior. | ||
const lines = result.stdout.replace(/\r?\n$/, "").split(/\r?\n/) | ||
for (let i = 0; i < lines.length; ++i) { | ||
const [key, value] = lines[i].split(/=(.*)/) | ||
if (key.length === 0 || typeof value === "undefined") { | ||
throw new Error(`Malformed line from header command: [${lines[i]}] (out: ${result.stdout})`) | ||
} | ||
headers[key] = value | ||
} | ||
} | ||
return headers | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters