@catladder/cli 0.0.0-next-aea5d3ad → 0.0.0-next-2b60440e
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/dist/bundles/cli/index.js +705 -229
- package/dist/bundles/runner-images/semantic-release/Dockerfile +15 -12
- package/dist/bundles/runner-images/semantic-release/scripts/semanticRelease +10 -1
- package/dist/bundles/skills/catladder-cli/references/commands.md +37 -2
- package/dist/bundles/skills/catladder-secrets/SKILL.md +42 -9
- package/dist/cli/src/commands/index.d.ts +4 -0
- package/dist/cli/src/commands/index.js +9 -1
- package/dist/cli/src/commands/index.js.map +1 -1
- package/dist/cli/src/commands/project/commandConfigSecrets.d.ts +6 -1
- package/dist/cli/src/commands/project/commandConfigSecrets.js +56 -15
- package/dist/cli/src/commands/project/commandConfigSecrets.js.map +1 -1
- package/dist/cli/src/commands/project/secrets/commandSecretsList.d.ts +24 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsList.js +64 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsList.js.map +1 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsPull.d.ts +19 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsPull.js +48 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsPull.js.map +1 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsPush.d.ts +19 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsPush.js +74 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsPush.js.map +1 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsSet.d.ts +24 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsSet.js +82 -0
- package/dist/cli/src/commands/project/secrets/commandSecretsSet.js.map +1 -0
- package/dist/cli/src/commands/project/secrets/document.d.ts +47 -0
- package/dist/cli/src/commands/project/secrets/document.js +180 -0
- package/dist/cli/src/commands/project/secrets/document.js.map +1 -0
- package/dist/cli/src/commands/project/secrets/parseKeysInput.d.ts +2 -0
- package/dist/cli/src/commands/project/secrets/parseKeysInput.js +10 -0
- package/dist/cli/src/commands/project/secrets/parseKeysInput.js.map +1 -0
- package/dist/cli/src/commands/project/secrets/scope.d.ts +35 -0
- package/dist/cli/src/commands/project/secrets/scope.js +113 -0
- package/dist/cli/src/commands/project/secrets/scope.js.map +1 -0
- package/dist/cli/src/utils/readStdin.d.ts +2 -0
- package/dist/cli/src/utils/readStdin.js +13 -0
- package/dist/cli/src/utils/readStdin.js.map +1 -0
- package/dist/runner-images/semantic-release/Dockerfile +15 -12
- package/dist/runner-images/semantic-release/scripts/semanticRelease +10 -1
- package/dist/skills/catladder-cli/references/commands.md +37 -2
- package/dist/skills/catladder-secrets/SKILL.md +42 -9
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/commands/index.ts +4 -0
- package/src/commands/project/commandConfigSecrets.ts +76 -15
- package/src/commands/project/secrets/__tests__/document.test.ts +122 -0
- package/src/commands/project/secrets/commandSecretsList.ts +75 -0
- package/src/commands/project/secrets/commandSecretsPull.ts +55 -0
- package/src/commands/project/secrets/commandSecretsPush.ts +100 -0
- package/src/commands/project/secrets/commandSecretsSet.ts +102 -0
- package/src/commands/project/secrets/document.ts +269 -0
- package/src/commands/project/secrets/parseKeysInput.ts +8 -0
- package/src/commands/project/secrets/scope.ts +151 -0
- package/src/utils/readStdin.ts +8 -0
- package/dist/cli/src/apps/cli/commands/project/commandConfigSecrets.d.ts +0 -2
- package/dist/cli/src/apps/cli/commands/project/commandConfigSecrets.js +0 -206
- package/dist/cli/src/apps/cli/commands/project/commandConfigSecrets.js.map +0 -1
- package/src/apps/cli/commands/project/commandConfigSecrets.ts +0 -322
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { Config } from "@catladder/pipeline";
|
|
2
|
+
import {
|
|
3
|
+
getEnabledPipelineTypes,
|
|
4
|
+
getSecretVarName,
|
|
5
|
+
getVaultConfig,
|
|
6
|
+
} from "@catladder/pipeline";
|
|
7
|
+
import {
|
|
8
|
+
getEnvironment,
|
|
9
|
+
getProjectConfig,
|
|
10
|
+
} from "../../../config/getProjectConfig";
|
|
11
|
+
import type { IO } from "../../../core/types";
|
|
12
|
+
import { isGhAuthenticated } from "../../../utils/github";
|
|
13
|
+
import { upsertAllVariables } from "../../../utils/gitlab";
|
|
14
|
+
import {
|
|
15
|
+
collectSecretTargets,
|
|
16
|
+
getConfiguredGithubRepo,
|
|
17
|
+
pushSecretsToGithub,
|
|
18
|
+
} from "../commandSecretsSyncGithub";
|
|
19
|
+
import type { ScopedSecretKeys } from "./scope";
|
|
20
|
+
|
|
21
|
+
/** placeholder for secrets that have no value in the vault yet */
|
|
22
|
+
export const FILL_ME = "🚨 FILL ME";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* component → env → key → value: the document shape shared by the
|
|
26
|
+
* editor, `secrets-pull` and `secrets-push`
|
|
27
|
+
*/
|
|
28
|
+
export type SecretsDocument = {
|
|
29
|
+
[componentName: string]: {
|
|
30
|
+
[env: string]: Record<string, unknown>;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** secrets of one env of one component, ready to be written */
|
|
35
|
+
export type SecretsWrite = {
|
|
36
|
+
env: string;
|
|
37
|
+
componentName: string;
|
|
38
|
+
secrets: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/* for convenience, parse json values. that makes it easier to edit secrets that are objects */
|
|
42
|
+
const parseJsonValue = (value: string): unknown => {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(value);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* builds the document for the given scope with the current values from
|
|
52
|
+
* the vault; unset secrets appear as the FILL_ME placeholder
|
|
53
|
+
*/
|
|
54
|
+
export const buildSecretsDocument = async (
|
|
55
|
+
io: IO,
|
|
56
|
+
scoped: ScopedSecretKeys,
|
|
57
|
+
): Promise<SecretsDocument> => {
|
|
58
|
+
const names = scoped.flatMap(({ env, componentName, keys }) =>
|
|
59
|
+
keys.map((key) => getSecretVarName(env, componentName, key)),
|
|
60
|
+
);
|
|
61
|
+
const values = await (await io.getVaultManager()).readSecrets(names, io);
|
|
62
|
+
|
|
63
|
+
const document: SecretsDocument = {};
|
|
64
|
+
for (const { env, componentName, keys } of scoped) {
|
|
65
|
+
document[componentName] ??= {};
|
|
66
|
+
document[componentName][env] = Object.fromEntries(
|
|
67
|
+
keys.map((key) => {
|
|
68
|
+
const value = values[getSecretVarName(env, componentName, key)];
|
|
69
|
+
return [key, value === undefined ? FILL_ME : parseJsonValue(value)];
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return document;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* validates a document against the scope. Undeclared components, envs
|
|
78
|
+
* or keys are always problems; missing keys only when `requireComplete`
|
|
79
|
+
* (the guided editor flow) — pushes may be partial.
|
|
80
|
+
*/
|
|
81
|
+
export const checkSecretsDocument = (
|
|
82
|
+
document: SecretsDocument,
|
|
83
|
+
scoped: ScopedSecretKeys,
|
|
84
|
+
{ requireComplete }: { requireComplete: boolean },
|
|
85
|
+
): string[] => {
|
|
86
|
+
const problems: string[] = [];
|
|
87
|
+
|
|
88
|
+
const scopedKeys = new Map<string, string[]>();
|
|
89
|
+
scoped.forEach(({ env, componentName, keys }) =>
|
|
90
|
+
scopedKeys.set(`${env}:${componentName}`, keys),
|
|
91
|
+
);
|
|
92
|
+
const scopedComponents = new Set(scoped.map((s) => s.componentName));
|
|
93
|
+
|
|
94
|
+
for (const [componentName, envs] of Object.entries(document ?? {})) {
|
|
95
|
+
if (!scopedComponents.has(componentName)) {
|
|
96
|
+
problems.push(
|
|
97
|
+
`"${componentName}" is not a component in this scope — remove it or widen the scope`,
|
|
98
|
+
);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
for (const [env, secrets] of Object.entries(envs ?? {})) {
|
|
102
|
+
const keys = scopedKeys.get(`${env}:${componentName}`);
|
|
103
|
+
if (!keys) {
|
|
104
|
+
problems.push(
|
|
105
|
+
`"${env}:${componentName}" is not in this scope — remove it or widen the scope`,
|
|
106
|
+
);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const extraneous = Object.keys(secrets ?? {}).filter(
|
|
110
|
+
(key) => !keys.includes(key),
|
|
111
|
+
);
|
|
112
|
+
extraneous.forEach((key) =>
|
|
113
|
+
problems.push(
|
|
114
|
+
`"${env}:${componentName}": "${key}" is not declared in the config (or filtered out by --key)`,
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (requireComplete) {
|
|
121
|
+
for (const { env, componentName, keys } of scoped) {
|
|
122
|
+
const provided = Object.keys(document?.[componentName]?.[env] ?? {});
|
|
123
|
+
keys
|
|
124
|
+
.filter((key) => !provided.includes(key))
|
|
125
|
+
.forEach((key) =>
|
|
126
|
+
problems.push(
|
|
127
|
+
`"${env}:${componentName}": "${key}" has not been provided`,
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return problems;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* turns a (validated) document into writes. FILL_ME placeholders and
|
|
138
|
+
* empty (null) values are skipped, so an untouched placeholder can
|
|
139
|
+
* never end up as a live secret value.
|
|
140
|
+
*/
|
|
141
|
+
export const collectSecretsWrites = (
|
|
142
|
+
document: SecretsDocument,
|
|
143
|
+
scoped: ScopedSecretKeys,
|
|
144
|
+
): { writes: SecretsWrite[]; skipped: string[] } => {
|
|
145
|
+
const writes: SecretsWrite[] = [];
|
|
146
|
+
const skipped: string[] = [];
|
|
147
|
+
for (const { env, componentName, keys } of scoped) {
|
|
148
|
+
const provided = document?.[componentName]?.[env] ?? {};
|
|
149
|
+
const secrets: Record<string, unknown> = {};
|
|
150
|
+
for (const key of keys) {
|
|
151
|
+
if (!(key in provided)) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const value = provided[key];
|
|
155
|
+
if (value === FILL_ME || value === null || value === undefined) {
|
|
156
|
+
skipped.push(`${env}:${componentName} ${key}`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
secrets[key] = value;
|
|
160
|
+
}
|
|
161
|
+
if (Object.keys(secrets).length > 0) {
|
|
162
|
+
writes.push({ env, componentName, secrets });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { writes, skipped };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* writes secrets to the vault (the source of truth) and mirrors them
|
|
170
|
+
* to every enabled CI backend — which backends exist is invisible to
|
|
171
|
+
* the caller
|
|
172
|
+
*/
|
|
173
|
+
export const writeSecretsAndMirror = async (
|
|
174
|
+
io: IO,
|
|
175
|
+
writes: SecretsWrite[],
|
|
176
|
+
): Promise<void> => {
|
|
177
|
+
const config = await getProjectConfig();
|
|
178
|
+
if (!config) {
|
|
179
|
+
throw new Error("no catladder config found");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
io.log("");
|
|
183
|
+
io.log("writing secrets to the vault, please wait...");
|
|
184
|
+
io.log("");
|
|
185
|
+
for (const { env, componentName, secrets } of writes) {
|
|
186
|
+
io.log("writing " + env + ":" + componentName + "...\n");
|
|
187
|
+
await (
|
|
188
|
+
await io.getVaultManager()
|
|
189
|
+
).writeSecrets(env, componentName, secrets);
|
|
190
|
+
io.log("");
|
|
191
|
+
io.log("✅ " + env + ":" + componentName);
|
|
192
|
+
io.log("--------------------------------\n");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await mirrorSecretsToCiBackends(io, config, writes);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* the vault is the source of truth; every enabled CI backend receives
|
|
200
|
+
* a mirrored copy of the written secrets (gitlab variables, github
|
|
201
|
+
* secrets), so all CI systems stay in sync
|
|
202
|
+
*/
|
|
203
|
+
const mirrorSecretsToCiBackends = async (
|
|
204
|
+
io: IO,
|
|
205
|
+
config: Config,
|
|
206
|
+
writes: SecretsWrite[],
|
|
207
|
+
) => {
|
|
208
|
+
const enabled = getEnabledPipelineTypes(config);
|
|
209
|
+
|
|
210
|
+
// gitlab mirror (unless gitlab is the vault itself, then it is
|
|
211
|
+
// already written)
|
|
212
|
+
if (getVaultConfig(config).type !== "gitlab" && enabled.includes("gitlab")) {
|
|
213
|
+
for (const { env, componentName, secrets } of writes) {
|
|
214
|
+
await upsertAllVariables(io, secrets, env, componentName);
|
|
215
|
+
}
|
|
216
|
+
io.log("✅ mirrored to gitlab");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!enabled.includes("github")) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const kinds = new Map<string, "secret" | "variable">();
|
|
223
|
+
for (const { env, componentName } of writes) {
|
|
224
|
+
const { secretEnvVarKeys, jobOnlyVars } = await getEnvironment(
|
|
225
|
+
env,
|
|
226
|
+
componentName,
|
|
227
|
+
);
|
|
228
|
+
[
|
|
229
|
+
...secretEnvVarKeys,
|
|
230
|
+
...jobOnlyVars.build.secretEnvVarKeys,
|
|
231
|
+
...jobOnlyVars.deploy.secretEnvVarKeys,
|
|
232
|
+
].forEach(({ key, kind }) =>
|
|
233
|
+
kinds.set(getSecretVarName(env, componentName, key), kind ?? "secret"),
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const secrets = writes.flatMap(({ env, componentName, secrets }) =>
|
|
237
|
+
Object.entries(secrets).map(([key, value]) => {
|
|
238
|
+
const name = getSecretVarName(env, componentName, key);
|
|
239
|
+
return {
|
|
240
|
+
name,
|
|
241
|
+
value: typeof value === "string" ? value : JSON.stringify(value ?? ""),
|
|
242
|
+
kind: kinds.get(name) ?? ("secret" as const),
|
|
243
|
+
};
|
|
244
|
+
}),
|
|
245
|
+
);
|
|
246
|
+
if (secrets.length === 0) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (!(await isGhAuthenticated())) {
|
|
250
|
+
io.log(
|
|
251
|
+
"⚠️ github pipeline is enabled, but the github cli (gh) is not authenticated — run `project secrets-sync-github` later to mirror the secrets",
|
|
252
|
+
);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const repo = await getConfiguredGithubRepo();
|
|
256
|
+
if (!repo) {
|
|
257
|
+
io.log(
|
|
258
|
+
"⚠️ github pipeline is enabled, but no github repository was found — run `project secrets-sync-github` later to mirror the secrets",
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
io.log(`mirroring ${secrets.length} secrets to github ('${repo}')...`);
|
|
263
|
+
await pushSecretsToGithub(
|
|
264
|
+
io,
|
|
265
|
+
repo,
|
|
266
|
+
secrets,
|
|
267
|
+
await collectSecretTargets(config),
|
|
268
|
+
);
|
|
269
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** parses a comma-separated `--key` input into a list of keys */
|
|
2
|
+
export const parseKeysInput = (input?: string): string[] | undefined => {
|
|
3
|
+
const keys = input
|
|
4
|
+
?.split(",")
|
|
5
|
+
.map((key) => key.trim())
|
|
6
|
+
.filter(Boolean);
|
|
7
|
+
return keys && keys.length > 0 ? keys : undefined;
|
|
8
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAllComponentsWithAllEnvsHierarchical,
|
|
3
|
+
getEnvironment,
|
|
4
|
+
} from "../../../config/getProjectConfig";
|
|
5
|
+
|
|
6
|
+
/** which envs of which components a secrets command operates on */
|
|
7
|
+
export type SecretsScope = {
|
|
8
|
+
[componentName: string]: string[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* resolves a scope expression to component → envs:
|
|
13
|
+
* - (empty): all components, all envs
|
|
14
|
+
* - `env:component`: one env of one component
|
|
15
|
+
* - `env` / `env:`: one env, every component that has it
|
|
16
|
+
* - `:component`: every env of one component
|
|
17
|
+
*/
|
|
18
|
+
export const resolveSecretsScope = async (
|
|
19
|
+
scope?: string,
|
|
20
|
+
): Promise<SecretsScope> => {
|
|
21
|
+
const all = await getAllComponentsWithAllEnvsHierarchical();
|
|
22
|
+
if (Object.keys(all).length === 0) {
|
|
23
|
+
throw new Error("no catladder config found");
|
|
24
|
+
}
|
|
25
|
+
if (!scope) {
|
|
26
|
+
return all;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const [env, componentName] = scope.split(":").map((x) => x || null);
|
|
30
|
+
|
|
31
|
+
if (componentName && !(componentName in all)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`unknown component "${componentName}" — components: ${Object.keys(all).join(", ")}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const scoped = Object.fromEntries(
|
|
38
|
+
Object.entries(all)
|
|
39
|
+
.filter(([name]) => !componentName || name === componentName)
|
|
40
|
+
.map(([name, envs]): [string, string[]] => [
|
|
41
|
+
name,
|
|
42
|
+
env ? envs.filter((e) => e === env) : envs,
|
|
43
|
+
])
|
|
44
|
+
.filter(([, envs]) => envs.length > 0),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (Object.keys(scoped).length === 0) {
|
|
48
|
+
const allEnvs = [...new Set(Object.values(all).flat())];
|
|
49
|
+
throw new Error(
|
|
50
|
+
`no component has the environment "${env}" — environments: ${allEnvs.join(", ")}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return scoped;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* all valid scope expressions, for autocompletion: `env:component`
|
|
58
|
+
* pairs plus the `env:` (one env, all components) and `:component`
|
|
59
|
+
* (one component, all envs) forms
|
|
60
|
+
*/
|
|
61
|
+
export const secretsScopeChoices = async (): Promise<string[]> => {
|
|
62
|
+
const all = await getAllComponentsWithAllEnvsHierarchical();
|
|
63
|
+
const pairs = Object.entries(all).flatMap(([componentName, envs]) =>
|
|
64
|
+
envs.map((env) => `${env}:${componentName}`),
|
|
65
|
+
);
|
|
66
|
+
const envs = [...new Set(Object.values(all).flat())];
|
|
67
|
+
return [
|
|
68
|
+
...pairs,
|
|
69
|
+
...envs.map((env) => `${env}:`),
|
|
70
|
+
...Object.keys(all).map((componentName) => `:${componentName}`),
|
|
71
|
+
];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** one env of one component together with its (filtered) secret keys */
|
|
75
|
+
export type ScopedSecretKeys = Array<{
|
|
76
|
+
env: string;
|
|
77
|
+
componentName: string;
|
|
78
|
+
keys: string[];
|
|
79
|
+
}>;
|
|
80
|
+
|
|
81
|
+
/** the secret keys declared in the config for one env of one component */
|
|
82
|
+
export const getDeclaredSecretKeys = async (
|
|
83
|
+
env: string,
|
|
84
|
+
componentName: string,
|
|
85
|
+
): Promise<string[]> => {
|
|
86
|
+
const { secretEnvVarKeys, jobOnlyVars } = await getEnvironment(
|
|
87
|
+
env,
|
|
88
|
+
componentName,
|
|
89
|
+
);
|
|
90
|
+
return [
|
|
91
|
+
...jobOnlyVars.build.secretEnvVarKeys,
|
|
92
|
+
...jobOnlyVars.deploy.secretEnvVarKeys,
|
|
93
|
+
...secretEnvVarKeys,
|
|
94
|
+
]
|
|
95
|
+
.filter((k) => !k.hidden)
|
|
96
|
+
.map((k) => k.key);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* expands a scope to its declared secret keys, optionally filtered to
|
|
101
|
+
* `onlyKeys`. Throws when a requested key is not declared anywhere in
|
|
102
|
+
* the scope (typo protection); env/component pairs without matching
|
|
103
|
+
* keys are dropped.
|
|
104
|
+
*/
|
|
105
|
+
export const getScopedSecretKeys = async (
|
|
106
|
+
scope: SecretsScope,
|
|
107
|
+
onlyKeys?: string[],
|
|
108
|
+
): Promise<ScopedSecretKeys> => {
|
|
109
|
+
const scoped: ScopedSecretKeys = [];
|
|
110
|
+
const declaredAnywhere = new Set<string>();
|
|
111
|
+
for (const [componentName, envs] of Object.entries(scope)) {
|
|
112
|
+
for (const env of envs) {
|
|
113
|
+
const declared = await getDeclaredSecretKeys(env, componentName);
|
|
114
|
+
declared.forEach((key) => declaredAnywhere.add(key));
|
|
115
|
+
const keys = onlyKeys
|
|
116
|
+
? declared.filter((key) => onlyKeys.includes(key))
|
|
117
|
+
: declared;
|
|
118
|
+
if (keys.length > 0) {
|
|
119
|
+
scoped.push({ env, componentName, keys });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const unknownKeys = (onlyKeys ?? []).filter(
|
|
125
|
+
(key) => !declaredAnywhere.has(key),
|
|
126
|
+
);
|
|
127
|
+
if (unknownKeys.length > 0) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`these secrets are not declared in this scope: ${unknownKeys.join(", ")}` +
|
|
130
|
+
`\ndeclared secrets: ${[...declaredAnywhere].join(", ")}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (scoped.length === 0) {
|
|
134
|
+
throw new Error("there are no secrets to configure in this scope");
|
|
135
|
+
}
|
|
136
|
+
return scoped;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** human-readable one-line-per-component summary of a scope */
|
|
140
|
+
export const formatScope = (scoped: ScopedSecretKeys): string => {
|
|
141
|
+
const byComponent = new Map<string, string[]>();
|
|
142
|
+
for (const { env, componentName } of scoped) {
|
|
143
|
+
byComponent.set(componentName, [
|
|
144
|
+
...(byComponent.get(componentName) ?? []),
|
|
145
|
+
env,
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
return [...byComponent.entries()]
|
|
149
|
+
.map(([componentName, envs]) => `- ${componentName}: ${envs.join(", ")}`)
|
|
150
|
+
.join("\n");
|
|
151
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** reads the whole stdin stream (for piped input in non-interactive use) */
|
|
2
|
+
export const readStdin = async (): Promise<string> => {
|
|
3
|
+
const chunks: Buffer[] = [];
|
|
4
|
+
for await (const chunk of process.stdin) {
|
|
5
|
+
chunks.push(chunk as Buffer);
|
|
6
|
+
}
|
|
7
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
8
|
+
};
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.projectConfigSecrets = void 0;
|
|
4
|
-
const pipeline_1 = require("../../../../../../pipeline/src/index.js");
|
|
5
|
-
const common_tags_1 = require("common-tags");
|
|
6
|
-
const lodash_es_1 = require("lodash-es");
|
|
7
|
-
const getProjectConfig_1 = require("../../../../config/getProjectConfig");
|
|
8
|
-
const commandSecretsSyncGithub_1 = require("../../../../commands/project/commandSecretsSyncGithub");
|
|
9
|
-
const editAsFile_1 = require("../../../../utils/editAsFile");
|
|
10
|
-
const github_1 = require("../../../../utils/github");
|
|
11
|
-
const gitlab_1 = require("../../../../utils/gitlab");
|
|
12
|
-
const promise_1 = require("../../../../utils/promise");
|
|
13
|
-
/* for convenience, parse json objects. that makes it easier to edit secrets that are object */
|
|
14
|
-
const resolveJson = (v) => Object.fromEntries(Object.entries(v).map(([componentName, envs]) => {
|
|
15
|
-
return [
|
|
16
|
-
componentName,
|
|
17
|
-
Object.fromEntries(Object.entries(envs).map(([env, secrets]) => [
|
|
18
|
-
env,
|
|
19
|
-
Object.fromEntries(Object.entries(secrets).map(([key, value]) => {
|
|
20
|
-
try {
|
|
21
|
-
return [key, JSON.parse(value)];
|
|
22
|
-
}
|
|
23
|
-
catch (e) {
|
|
24
|
-
return [key, value];
|
|
25
|
-
}
|
|
26
|
-
})),
|
|
27
|
-
])),
|
|
28
|
-
];
|
|
29
|
-
}));
|
|
30
|
-
const getSecretEnvVarKeysToConfigure = async (env, componentName) => {
|
|
31
|
-
const { secretEnvVarKeys, jobOnlyVars } = await (0, getProjectConfig_1.getEnvironment)(env, componentName);
|
|
32
|
-
return [
|
|
33
|
-
...jobOnlyVars.build.secretEnvVarKeys,
|
|
34
|
-
...jobOnlyVars.deploy.secretEnvVarKeys,
|
|
35
|
-
...secretEnvVarKeys,
|
|
36
|
-
]
|
|
37
|
-
.filter((k) => !k.hidden)
|
|
38
|
-
.map((k) => k.key);
|
|
39
|
-
};
|
|
40
|
-
const getEnvVarsToEdit = async (instance, env, componentName) => {
|
|
41
|
-
const secretEnvVarKeys = await getSecretEnvVarKeysToConfigure(env, componentName);
|
|
42
|
-
const normalEnvVars = await (0, getProjectConfig_1.getEnvVarsResolved)(instance, env, componentName);
|
|
43
|
-
const jobOnlyEnvVars = await (0, getProjectConfig_1.getJobOnlyEnvVarsResolved)(instance, env, componentName);
|
|
44
|
-
const allEnvVars = {
|
|
45
|
-
...normalEnvVars,
|
|
46
|
-
...jobOnlyEnvVars,
|
|
47
|
-
};
|
|
48
|
-
return Object.fromEntries(secretEnvVarKeys.map((key) => {
|
|
49
|
-
const value = allEnvVars[key];
|
|
50
|
-
// due to some quirky way to resolve these variables, unset variables have the $CL_ prefix, so we remove thouse here
|
|
51
|
-
const variableIsNotSet = value === "$" + (0, pipeline_1.getSecretVarName)(env, componentName, key);
|
|
52
|
-
return [key, variableIsNotSet ? "🚨 FILL ME" : value];
|
|
53
|
-
}));
|
|
54
|
-
};
|
|
55
|
-
const doItFor = async (instance, envAndComponents) => {
|
|
56
|
-
let valuesToEdit = Object.fromEntries(await Promise.all(Object.entries(envAndComponents).map(async ([componentName, envs]) => [
|
|
57
|
-
componentName,
|
|
58
|
-
Object.fromEntries(await Promise.all(envs.map(async (env) => [
|
|
59
|
-
env,
|
|
60
|
-
await getEnvVarsToEdit(instance, env, componentName),
|
|
61
|
-
]))),
|
|
62
|
-
])));
|
|
63
|
-
let hasErrors = true;
|
|
64
|
-
while (hasErrors) {
|
|
65
|
-
valuesToEdit = await (0, editAsFile_1.editAsFile)(resolveJson(valuesToEdit), (0, common_tags_1.stripIndents) `
|
|
66
|
-
Please fill in all secrets for:
|
|
67
|
-
|
|
68
|
-
${Object.entries(envAndComponents)
|
|
69
|
-
.map(([componentName, envs]) => `- ${componentName}: ${envs.join(", ")}`)
|
|
70
|
-
.join("\n")}
|
|
71
|
-
|
|
72
|
-
`);
|
|
73
|
-
// check for errors
|
|
74
|
-
hasErrors = false;
|
|
75
|
-
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
76
|
-
for (const env of envs) {
|
|
77
|
-
const usedKeys = valuesToEdit[componentName][env]
|
|
78
|
-
? Object.keys(valuesToEdit[componentName][env])
|
|
79
|
-
: [];
|
|
80
|
-
// check whether newValues have the exact number of keys
|
|
81
|
-
const secretEnvVarKeys = await getSecretEnvVarKeysToConfigure(env, componentName);
|
|
82
|
-
const extranous = (0, lodash_es_1.difference)(usedKeys, secretEnvVarKeys);
|
|
83
|
-
const missing = (0, lodash_es_1.difference)(secretEnvVarKeys, usedKeys);
|
|
84
|
-
if (extranous.length > 0 || missing.length > 0) {
|
|
85
|
-
instance.log("");
|
|
86
|
-
instance.log(`😿 Oh no! There is something wrong with "${componentName}"`);
|
|
87
|
-
instance.log("");
|
|
88
|
-
if (extranous.length > 0) {
|
|
89
|
-
instance.log("these secrets are not declared in the config");
|
|
90
|
-
extranous.forEach((key) => instance.log(key));
|
|
91
|
-
instance.log("");
|
|
92
|
-
}
|
|
93
|
-
if (missing.length > 0) {
|
|
94
|
-
instance.log("these secrets have not been provided:");
|
|
95
|
-
missing.forEach((key) => instance.log(key));
|
|
96
|
-
instance.log("");
|
|
97
|
-
}
|
|
98
|
-
await (0, promise_1.delay)(1000);
|
|
99
|
-
const shouldContinue = await instance.confirm("Try again? 🤔");
|
|
100
|
-
if (!shouldContinue) {
|
|
101
|
-
throw new Error("abort");
|
|
102
|
-
}
|
|
103
|
-
hasErrors = true;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const config = await (0, getProjectConfig_1.getProjectConfig)();
|
|
109
|
-
if (!config) {
|
|
110
|
-
throw new Error("no catladder config found");
|
|
111
|
-
}
|
|
112
|
-
instance.log("");
|
|
113
|
-
instance.log("writing all secrets to the vault, please wait...");
|
|
114
|
-
instance.log("");
|
|
115
|
-
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
116
|
-
for (const env of envs) {
|
|
117
|
-
instance.log("writing " + env + ":" + componentName + "...\n");
|
|
118
|
-
await (await instance.getVaultManager()).writeSecrets(env, componentName, valuesToEdit[componentName][env]);
|
|
119
|
-
instance.log("");
|
|
120
|
-
instance.log("✅ " + env + ":" + componentName);
|
|
121
|
-
instance.log("--------------------------------\n");
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
await mirrorSecretsToCiBackends(instance, config, envAndComponents, valuesToEdit);
|
|
125
|
-
instance.log("done! 😻");
|
|
126
|
-
instance.log("");
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
|
-
* the vault is the source of truth; every enabled CI backend receives
|
|
130
|
-
* a mirrored copy of the edited secrets (gitlab variables, github
|
|
131
|
-
* secrets), so all CI systems stay in sync
|
|
132
|
-
*/
|
|
133
|
-
const mirrorSecretsToCiBackends = async (instance, config, envAndComponents, valuesToEdit) => {
|
|
134
|
-
const enabled = (0, pipeline_1.getEnabledPipelineTypes)(config);
|
|
135
|
-
// gitlab mirror (unless gitlab is the vault itself, then it is
|
|
136
|
-
// already written)
|
|
137
|
-
if ((0, pipeline_1.getVaultConfig)(config).type !== "gitlab" && enabled.includes("gitlab")) {
|
|
138
|
-
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
139
|
-
for (const env of envs) {
|
|
140
|
-
await (0, gitlab_1.upsertAllVariables)(instance, valuesToEdit[componentName][env], env, componentName);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
instance.log("✅ mirrored to gitlab");
|
|
144
|
-
}
|
|
145
|
-
if (!enabled.includes("github")) {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const kinds = new Map();
|
|
149
|
-
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
150
|
-
for (const env of envs) {
|
|
151
|
-
const { secretEnvVarKeys, jobOnlyVars } = await (0, getProjectConfig_1.getEnvironment)(env, componentName);
|
|
152
|
-
[
|
|
153
|
-
...secretEnvVarKeys,
|
|
154
|
-
...jobOnlyVars.build.secretEnvVarKeys,
|
|
155
|
-
...jobOnlyVars.deploy.secretEnvVarKeys,
|
|
156
|
-
].forEach(({ key, kind }) => kinds.set((0, pipeline_1.getSecretVarName)(env, componentName, key), kind !== null && kind !== void 0 ? kind : "secret"));
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
const secrets = Object.entries(envAndComponents).flatMap(([componentName, envs]) => envs.flatMap((env) => {
|
|
160
|
-
var _a;
|
|
161
|
-
return Object.entries((_a = valuesToEdit[componentName][env]) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => {
|
|
162
|
-
var _a;
|
|
163
|
-
const name = (0, pipeline_1.getSecretVarName)(env, componentName, key);
|
|
164
|
-
return {
|
|
165
|
-
name,
|
|
166
|
-
value: typeof value === "string" ? value : JSON.stringify(value !== null && value !== void 0 ? value : ""),
|
|
167
|
-
kind: (_a = kinds.get(name)) !== null && _a !== void 0 ? _a : "secret",
|
|
168
|
-
};
|
|
169
|
-
});
|
|
170
|
-
}));
|
|
171
|
-
if (secrets.length === 0) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (!(await (0, github_1.isGhAuthenticated)())) {
|
|
175
|
-
instance.log("⚠️ github pipeline is enabled, but the github cli (gh) is not authenticated — run `project secrets-sync-github` later to mirror the secrets");
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const repo = await (0, commandSecretsSyncGithub_1.getConfiguredGithubRepo)();
|
|
179
|
-
if (!repo) {
|
|
180
|
-
instance.log("⚠️ github pipeline is enabled, but no github repository was found — run `project secrets-sync-github` later to mirror the secrets");
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
instance.log(`mirroring ${secrets.length} secrets to github ('${repo}')...`);
|
|
184
|
-
await (0, commandSecretsSyncGithub_1.pushSecretsToGithub)(instance, repo, secrets, await (0, commandSecretsSyncGithub_1.collectSecretTargets)(config));
|
|
185
|
-
};
|
|
186
|
-
const projectConfigSecrets = async (io, envComponent) => {
|
|
187
|
-
if (!envComponent) {
|
|
188
|
-
const allEnvAndcomponents = await (0, getProjectConfig_1.getAllComponentsWithAllEnvsHierarchical)();
|
|
189
|
-
await doItFor(io, allEnvAndcomponents);
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
const { env, componentName } = (0, getProjectConfig_1.parseChoice)(envComponent);
|
|
193
|
-
// componentName can be null. in this case, iterate over all components
|
|
194
|
-
if (!componentName) {
|
|
195
|
-
const components = await (0, getProjectConfig_1.getProjectComponents)();
|
|
196
|
-
await doItFor(io, Object.fromEntries(components.map((c) => [c, [env]])));
|
|
197
|
-
}
|
|
198
|
-
if (componentName) {
|
|
199
|
-
await doItFor(io, {
|
|
200
|
-
[componentName]: [env],
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
exports.projectConfigSecrets = projectConfigSecrets;
|
|
206
|
-
//# sourceMappingURL=commandConfigSecrets.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"commandConfigSecrets.js","sourceRoot":"","sources":["../../../../../../../src/apps/cli/commands/project/commandConfigSecrets.ts"],"names":[],"mappings":"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"}
|