|
| 1 | +import * as cp from "child_process" |
| 2 | +import * as util from "util" |
| 3 | + |
| 4 | +// Credentials describes the expected JSON from the credentials process. |
| 5 | +export interface Credentials { |
| 6 | + // headers is a map of header name to value. |
| 7 | + headers: Record<string, string> |
| 8 | +} |
| 9 | + |
| 10 | +export interface Logger { |
| 11 | + writeToCoderOutputChannel(message: string): void |
| 12 | +} |
| 13 | + |
| 14 | +interface ExecException { |
| 15 | + code?: number |
| 16 | + stderr?: string |
| 17 | + stdout?: string |
| 18 | +} |
| 19 | + |
| 20 | +function isExecException(err: unknown): err is ExecException { |
| 21 | + return typeof (err as ExecException).code !== "undefined" |
| 22 | +} |
| 23 | + |
| 24 | +// TODO: getCredentials might make more sense to directly implement on Storage |
| 25 | +// but it is difficult to test Storage right now since we use vitest instead of |
| 26 | +// the standard extension testing framework which would give us access to vscode |
| 27 | +// APIs. We should revert the testing framework then consider moving this. |
| 28 | + |
| 29 | +// getCredentials calls the credentials process and parses the JSON from |
| 30 | +// stdout. Both stdout and stderr are logged on error but stderr is otherwise |
| 31 | +// ignored. Throws an error if the process exits with non-zero or the JSON is |
| 32 | +// invalid. Returns undefined if there is no credentials process set. No |
| 33 | +// effort is made to validate the JSON other than making sure it can be |
| 34 | +// parsed. |
| 35 | +export async function getCredentials( |
| 36 | + url: string | undefined, |
| 37 | + command: string | undefined, |
| 38 | + logger: Logger, |
| 39 | +): Promise<Credentials | undefined> { |
| 40 | + if (typeof url === "string" && url.trim().length > 0 && typeof command === "string" && command.trim().length > 0) { |
| 41 | + try { |
| 42 | + const { stdout } = await util.promisify(cp.exec)(command, { |
| 43 | + env: { |
| 44 | + ...process.env, |
| 45 | + CODER_URL: url, |
| 46 | + }, |
| 47 | + }) |
| 48 | + try { |
| 49 | + return JSON.parse(stdout) |
| 50 | + } catch (error) { |
| 51 | + throw new Error(`Credentials process output malformed JSON: ${error}`) |
| 52 | + } |
| 53 | + } catch (error) { |
| 54 | + if (isExecException(error)) { |
| 55 | + logger.writeToCoderOutputChannel(`Credentials process exited unexpectedly with code ${error.code}`) |
| 56 | + logger.writeToCoderOutputChannel(`stdout: ${error.stdout}`) |
| 57 | + logger.writeToCoderOutputChannel(`stderr: ${error.stderr}`) |
| 58 | + throw new Error(`Credentials process exited unexpectedly with code ${error.code}`) |
| 59 | + } |
| 60 | + throw new Error(`Credentials process exited unexpectedly: ${error}`) |
| 61 | + } |
| 62 | + } |
| 63 | + return undefined |
| 64 | +} |
0 commit comments