Skip to content

Commit 8926272

Browse files
committed
Update shell implementation for deno
1 parent 0005afd commit 8926272

File tree

10 files changed

+2218
-1
lines changed

10 files changed

+2218
-1
lines changed

ext/deno/runbook/deno.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"imports": {
3+
"dax": "jsr:@david/dax@^0.42.0",
4+
"@std/assert": "jsr:@std/assert@1"
5+
}
6+
}

ext/deno/runbook/deno.lock

+1,733
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/deno/runbook/log.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as log from "jsr:@std/log";
2+
3+
export const timestampISOLocal = (date: Date) => {
4+
// subtract the offset from t
5+
const offset = date.getTime() - date.getTimezoneOffset() * 60 * 1000;
6+
// create shifted Date object
7+
const local = new Date(offset);
8+
// convert to ISO format string
9+
return local.toISOString();
10+
};
11+
12+
// Using .cache to avoid spreading temporary files across the repo
13+
// and to be clear that they're transient
14+
export const fileHandler = new log.FileHandler("INFO", {
15+
filename: [
16+
// TODO: decide how to populate this
17+
Deno.env.get("RUNBOOK_DIR"),
18+
"runbooks",
19+
".cache",
20+
"logs",
21+
"audit",
22+
"output.log",
23+
].join("/"),
24+
formatter: (record) =>
25+
JSON.stringify({
26+
level: record.levelName,
27+
message: record.msg,
28+
ts: timestampISOLocal(record.datetime),
29+
name: record.loggerName,
30+
// Note this requires we use logs with only a type signature of `log.info("message", {})`
31+
// not variadric usage
32+
args: record.args[0],
33+
}),
34+
});
35+
36+
log.setup({
37+
handlers: {
38+
// TODO: decide on re-enabling fileHandler
39+
// file: fileHandler,
40+
console: new log.ConsoleHandler("INFO"),
41+
},
42+
43+
loggers: {
44+
default: {
45+
level: "INFO",
46+
handlers: ["console"],
47+
},
48+
},
49+
});
50+
51+
export { log };

ext/deno/runbook/mod.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
export * from "./shell.ts";
2+
13
// Mod.ts for runbook-deno helpers
24
// Default Setup
35
// See deno dax module for capabilities, including $.request, retry, etc
4-
import {CommandBuilder, build$} from "jsr:@david/dax";
6+
import {CommandBuilder, build$} from "dax";
57

68
const commandBuilder = new CommandBuilder()
79
.stdout("inheritPiped")

ext/deno/runbook/secrets.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export interface Secrets {
2+
[key: string]: () => Promise<string>;
3+
}
4+
5+
export class SecureEnvironment {
6+
#secrets: Secrets;
7+
constructor(secrets: Secrets) {
8+
this.#secrets = secrets;
9+
}
10+
11+
async load(): Promise<{ [key: string]: string }> {
12+
const acc: { [key: string]: string } = {};
13+
for await (const [k, v] of Object.entries(this.#secrets)) {
14+
const resolved = await v();
15+
acc[k] = resolved;
16+
}
17+
18+
// Ensure that we don't have overlapping secret keys
19+
// because they will have undefined behavior during
20+
// replacements
21+
this.ensureNonOverlappingKeys(acc);
22+
return acc;
23+
}
24+
25+
ensureNonOverlappingKeys(kvs: { [key: string]: string }) {
26+
const keys = Object.keys(kvs);
27+
keys.forEach((key, _i) => {
28+
keys.forEach((kx, _ix) => {
29+
if (kx.includes(key) && key !== kx) {
30+
throw new Error(`Secrets cannot overlap: ${key} and ${kx}`);
31+
}
32+
});
33+
});
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import {
2+
GetParameterCommand,
3+
SSMClient,
4+
} from "npm:@aws-sdk/client-ssm@^3.315.0";
5+
6+
const REGION = 'us-east-1'
7+
8+
export const getParameterByNameBuilder = async (region: string): Promise<(name: string) => Promise<string>> => {
9+
return async (name: string) => {
10+
const ssmClient = new SSMClient({ region: REGION });
11+
try {
12+
const { Parameter } = await ssmClient.send(
13+
new GetParameterCommand({
14+
Name: name,
15+
WithDecryption: true,
16+
}),
17+
);
18+
19+
if (Parameter?.Value == null) {
20+
throw new Error(
21+
`No parameters found for ${name}.`,
22+
);
23+
}
24+
25+
return Parameter.Value;
26+
} finally {
27+
ssmClient.destroy();
28+
}
29+
30+
}
31+
};
32+
33+
export const getParameterByName = getParameterByNameBuilder(REGION)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { $ } from "jsr:@david/dax";
2+
3+
export async function getTokenFromVaultByPath(path: string) {
4+
const hasOp = await $.which("op");
5+
if (hasOp) {
6+
const result = await $
7+
.raw`op read "${path}"`
8+
.text();
9+
return result.trim();
10+
} else {
11+
throw new Error(
12+
"No way to get slack token from vault. Appears that 1password cli is failing. See 1password cli setup instructions and Try again.",
13+
);
14+
}
15+
}

ext/deno/runbook/shell.d.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export interface SecureCommandResultParams {
2+
timing: {
3+
duration_ms: number;
4+
start: string;
5+
end: string;
6+
};
7+
ts: string;
8+
code: number;
9+
command: string;
10+
stdout: string;
11+
stderr: string;
12+
}
13+
14+
export interface SecureCommandResultRaw {
15+
// stdout is the output of the command with trimEnd() applied
16+
stdout: string;
17+
stderr: string;
18+
code: number;
19+
command: string;
20+
}
21+
22+
export type RunOptions = {
23+
stdin?: string;
24+
verbose?: boolean;
25+
// TODO(zph): thread this through
26+
//env?: SecureEnvironment;
27+
noThrow?: boolean;
28+
dryRun?: boolean;
29+
};
30+
31+
export interface CommandResultStub {
32+
stdout: string;
33+
stderr: string;
34+
code: number;
35+
}

0 commit comments

Comments
 (0)