@nimbus-dev/connectors 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/connectors/apple/src/adapters.ts +56 -1
- package/connectors/apple/src/caldav-core.ts +7 -0
- package/connectors/apple/src/calendar-tools.ts +34 -0
- package/connectors/apple/src/tools.ts +2 -1
- package/connectors/aws/src/tools.ts +110 -0
- package/connectors/azure/src/tools.ts +70 -0
- package/connectors/circleci/src/tools.ts +63 -1
- package/connectors/gcp/src/tools.ts +74 -16
- package/connectors/github/src/tools.ts +298 -6
- package/connectors/gitlab/src/gitlab-files.ts +213 -0
- package/connectors/gitlab/src/tools.ts +226 -0
- package/connectors/kubernetes/src/tools.ts +35 -15
- package/connectors/slack/src/tools.ts +56 -5
- package/connectors/snowflake/src/server.ts +69 -2
- package/connectors/teams/src/bot-send.ts +10 -2
- package/connectors/teams/src/tools.ts +18 -1
- package/package.json +1 -1
- package/shared/audit-chain.ts +5 -2
- package/shared/commit-files.ts +138 -0
- package/shared/consent-kit.ts +141 -31
- package/shared/github-files.ts +195 -0
- package/shared/github-rest.ts +12 -0
- package/shared/k8s-deployment-view.ts +68 -0
- package/shared/prepare-tool-ids.ts +21 -0
- package/shared/private-kubeconfig.ts +110 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* private-kubeconfig — one kubeconfig file per tool call, never the owner's own.
|
|
3
|
+
*
|
|
4
|
+
* `kubectl` and `gcloud container clusters get-credentials` both read AND write a kubeconfig:
|
|
5
|
+
* gcloud writes `$HOME/.kube/config` unless `KUBECONFIG` is set (first path if it is a list), and
|
|
6
|
+
* kubectl takes the file `KUBECONFIG` names, or merges a list of them. Handed the owner's real
|
|
7
|
+
* file, a tool call could change it, and two concurrent calls would share and race on it. So each
|
|
8
|
+
* call works on a private copy in its own 0700 directory, removed afterwards, success or failure.
|
|
9
|
+
*
|
|
10
|
+
* Sources: https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
|
|
11
|
+
* (`--kubeconfig` set: only that file; else `KUBECONFIG` is a list, `:` on Linux and macOS, `;` on
|
|
12
|
+
* Windows) and https://docs.cloud.google.com/sdk/gcloud/reference/container/clusters/get-credentials
|
|
13
|
+
* (writes `HOME/.kube/config` unless `KUBECONFIG` is set).
|
|
14
|
+
*
|
|
15
|
+
* The copy is a plain file copy, not `kubectl config view --flatten`. One consequence is stated in
|
|
16
|
+
* the connector READMEs: a kubeconfig whose certificate paths are RELATIVE resolves them against the
|
|
17
|
+
* file's own directory, so a copy of such a file does not find them. Use absolute paths or
|
|
18
|
+
* embedded certificate data.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { constants } from "node:fs";
|
|
22
|
+
import { lstat, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
23
|
+
import { tmpdir } from "node:os";
|
|
24
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
25
|
+
|
|
26
|
+
/** Names the directory private kubeconfigs are made in; an absolute path, else `os.tmpdir()`. */
|
|
27
|
+
export const SCRATCH_DIR_ENV = "NIMBUS_CONNECTOR_SCRATCH_DIR";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The one path `raw` (a `KUBECONFIG` value) names. A list of paths throws: a tool that cannot say
|
|
31
|
+
* which file it works on cannot copy it. `separator` is the platform's `path.delimiter`, so a
|
|
32
|
+
* Windows drive path (`C:\Users\me\config`, whose `:` is not a separator there) is one path, and
|
|
33
|
+
* a `;` list is refused there; elsewhere a `:` list is.
|
|
34
|
+
*/
|
|
35
|
+
export function singleKubeconfigPath(
|
|
36
|
+
raw: string | undefined,
|
|
37
|
+
separator: string = delimiter,
|
|
38
|
+
): string {
|
|
39
|
+
const p = raw?.trim();
|
|
40
|
+
if (p === undefined || p === "") {
|
|
41
|
+
throw new Error("KUBECONFIG env must be set to a kubeconfig file path");
|
|
42
|
+
}
|
|
43
|
+
const pieces = p
|
|
44
|
+
.split(separator)
|
|
45
|
+
.map((piece) => piece.trim())
|
|
46
|
+
.filter((piece) => piece !== "");
|
|
47
|
+
if (pieces.length > 1) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`KUBECONFIG names more than one file (separator "${separator}"); set it to ONE kubeconfig path`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
// A value that is only separators names no file at all.
|
|
53
|
+
const only = pieces[0];
|
|
54
|
+
if (only === undefined) throw new Error("KUBECONFIG env must be set to a kubeconfig file path");
|
|
55
|
+
// The one piece that remains, not the raw value: `/a/config:` names `/a/config`, and the copy is
|
|
56
|
+
// made from that path, never from a string with a separator in it.
|
|
57
|
+
return only;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The base directory private kubeconfigs are made under. */
|
|
61
|
+
function scratchBase(): string {
|
|
62
|
+
const configured = process.env[SCRATCH_DIR_ENV]?.trim();
|
|
63
|
+
if (configured === undefined || configured === "") return tmpdir();
|
|
64
|
+
if (!isAbsolute(configured)) {
|
|
65
|
+
throw new Error(`${SCRATCH_DIR_ENV} must be an absolute path`);
|
|
66
|
+
}
|
|
67
|
+
return configured;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Refuse a source that is not a plain file: a symlink, a directory, a device. */
|
|
71
|
+
async function assertRegularFile(path: string): Promise<void> {
|
|
72
|
+
const st = await lstat(path).catch(() => undefined);
|
|
73
|
+
if (st === undefined) throw new Error("KUBECONFIG names a file that does not exist");
|
|
74
|
+
if (st.isSymbolicLink() || !st.isFile()) {
|
|
75
|
+
throw new Error("KUBECONFIG must name a regular file (not a symlink or a directory)");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run `body` with a private kubeconfig.
|
|
81
|
+
*
|
|
82
|
+
* With `source` (a `KUBECONFIG` value, ONE path): the file is copied, mode 0600, and `body` gets
|
|
83
|
+
* `{ KUBECONFIG: <copy> }`. Without it, the copy starts EMPTY, for `get-credentials` to fill.
|
|
84
|
+
* The directory is removed when `body` settles.
|
|
85
|
+
*/
|
|
86
|
+
export async function withPrivateKubeconfig<T>(
|
|
87
|
+
opts: { readonly source?: string | undefined },
|
|
88
|
+
body: (env: { readonly KUBECONFIG: string }) => Promise<T>,
|
|
89
|
+
): Promise<T> {
|
|
90
|
+
// Everything that can refuse does so BEFORE a directory exists.
|
|
91
|
+
let content: Buffer | undefined;
|
|
92
|
+
if (opts.source !== undefined) {
|
|
93
|
+
const sourcePath = singleKubeconfigPath(opts.source);
|
|
94
|
+
await assertRegularFile(sourcePath);
|
|
95
|
+
content = await readFile(sourcePath);
|
|
96
|
+
}
|
|
97
|
+
const base = scratchBase();
|
|
98
|
+
const dir = await mkdtemp(join(base, "nimbus-kubeconfig-"));
|
|
99
|
+
try {
|
|
100
|
+
const file = join(dir, "config");
|
|
101
|
+
// `wx`: the file is new by construction, and an existing one is an error, not a reuse.
|
|
102
|
+
await writeFile(file, content ?? "", {
|
|
103
|
+
mode: 0o600,
|
|
104
|
+
flag: constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
|
|
105
|
+
});
|
|
106
|
+
return await body({ KUBECONFIG: file });
|
|
107
|
+
} finally {
|
|
108
|
+
await rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
109
|
+
}
|
|
110
|
+
}
|