@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
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getEnabledPipelineTypes,
|
|
3
|
-
getSecretVarName,
|
|
4
|
-
getVaultConfig,
|
|
5
|
-
} from "@catladder/pipeline";
|
|
6
|
-
import type { Config } from "@catladder/pipeline";
|
|
7
|
-
import { stripIndents } from "common-tags";
|
|
8
|
-
import { difference } from "lodash-es";
|
|
9
|
-
import type { IO } from "../../../../core/types";
|
|
10
|
-
import {
|
|
11
|
-
getAllComponentsWithAllEnvsHierarchical,
|
|
12
|
-
getEnvironment,
|
|
13
|
-
getEnvVarsResolved,
|
|
14
|
-
getJobOnlyEnvVarsResolved,
|
|
15
|
-
getProjectComponents,
|
|
16
|
-
getProjectConfig,
|
|
17
|
-
parseChoice,
|
|
18
|
-
} from "../../../../config/getProjectConfig";
|
|
19
|
-
import {
|
|
20
|
-
collectSecretTargets,
|
|
21
|
-
getConfiguredGithubRepo,
|
|
22
|
-
pushSecretsToGithub,
|
|
23
|
-
} from "../../../../commands/project/commandSecretsSyncGithub";
|
|
24
|
-
import { editAsFile } from "../../../../utils/editAsFile";
|
|
25
|
-
import { isGhAuthenticated } from "../../../../utils/github";
|
|
26
|
-
import { upsertAllVariables } from "../../../../utils/gitlab";
|
|
27
|
-
import { delay } from "../../../../utils/promise";
|
|
28
|
-
|
|
29
|
-
type Vars = {
|
|
30
|
-
[env: string]: {
|
|
31
|
-
[componentName: string]: Record<string, string>;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
/* for convenience, parse json objects. that makes it easier to edit secrets that are object */
|
|
35
|
-
const resolveJson = (v: Vars) =>
|
|
36
|
-
Object.fromEntries(
|
|
37
|
-
Object.entries(v).map(([componentName, envs]) => {
|
|
38
|
-
return [
|
|
39
|
-
componentName,
|
|
40
|
-
Object.fromEntries(
|
|
41
|
-
Object.entries(envs).map(([env, secrets]) => [
|
|
42
|
-
env,
|
|
43
|
-
Object.fromEntries(
|
|
44
|
-
Object.entries(secrets).map(([key, value]) => {
|
|
45
|
-
try {
|
|
46
|
-
return [key, JSON.parse(value)];
|
|
47
|
-
} catch (e) {
|
|
48
|
-
return [key, value];
|
|
49
|
-
}
|
|
50
|
-
}),
|
|
51
|
-
),
|
|
52
|
-
]),
|
|
53
|
-
),
|
|
54
|
-
];
|
|
55
|
-
}),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
const getSecretEnvVarKeysToConfigure = async (
|
|
59
|
-
env: string,
|
|
60
|
-
componentName: string,
|
|
61
|
-
) => {
|
|
62
|
-
const { secretEnvVarKeys, jobOnlyVars } = await getEnvironment(
|
|
63
|
-
env,
|
|
64
|
-
componentName,
|
|
65
|
-
);
|
|
66
|
-
return [
|
|
67
|
-
...jobOnlyVars.build.secretEnvVarKeys,
|
|
68
|
-
...jobOnlyVars.deploy.secretEnvVarKeys,
|
|
69
|
-
...secretEnvVarKeys,
|
|
70
|
-
]
|
|
71
|
-
.filter((k) => !k.hidden)
|
|
72
|
-
.map((k) => k.key);
|
|
73
|
-
};
|
|
74
|
-
const getEnvVarsToEdit = async (
|
|
75
|
-
instance: IO,
|
|
76
|
-
env: string,
|
|
77
|
-
componentName: string,
|
|
78
|
-
) => {
|
|
79
|
-
const secretEnvVarKeys = await getSecretEnvVarKeysToConfigure(
|
|
80
|
-
env,
|
|
81
|
-
componentName,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
const normalEnvVars = await getEnvVarsResolved(instance, env, componentName);
|
|
85
|
-
const jobOnlyEnvVars = await getJobOnlyEnvVarsResolved(
|
|
86
|
-
instance,
|
|
87
|
-
env,
|
|
88
|
-
componentName,
|
|
89
|
-
);
|
|
90
|
-
const allEnvVars = {
|
|
91
|
-
...normalEnvVars,
|
|
92
|
-
...jobOnlyEnvVars,
|
|
93
|
-
};
|
|
94
|
-
return Object.fromEntries(
|
|
95
|
-
secretEnvVarKeys.map((key) => {
|
|
96
|
-
const value = allEnvVars[key];
|
|
97
|
-
// due to some quirky way to resolve these variables, unset variables have the $CL_ prefix, so we remove thouse here
|
|
98
|
-
const variableIsNotSet =
|
|
99
|
-
value === "$" + getSecretVarName(env, componentName, key);
|
|
100
|
-
return [key, variableIsNotSet ? "🚨 FILL ME" : value];
|
|
101
|
-
}),
|
|
102
|
-
);
|
|
103
|
-
};
|
|
104
|
-
const doItFor = async (
|
|
105
|
-
instance: IO,
|
|
106
|
-
envAndComponents: {
|
|
107
|
-
[componentName: string]: string[];
|
|
108
|
-
},
|
|
109
|
-
) => {
|
|
110
|
-
let valuesToEdit: Vars = Object.fromEntries(
|
|
111
|
-
await Promise.all(
|
|
112
|
-
Object.entries(envAndComponents).map(async ([componentName, envs]) => [
|
|
113
|
-
componentName,
|
|
114
|
-
Object.fromEntries(
|
|
115
|
-
await Promise.all(
|
|
116
|
-
envs.map(async (env) => [
|
|
117
|
-
env,
|
|
118
|
-
await getEnvVarsToEdit(instance, env, componentName),
|
|
119
|
-
]),
|
|
120
|
-
),
|
|
121
|
-
),
|
|
122
|
-
]),
|
|
123
|
-
),
|
|
124
|
-
);
|
|
125
|
-
let hasErrors = true;
|
|
126
|
-
while (hasErrors) {
|
|
127
|
-
valuesToEdit = await editAsFile(
|
|
128
|
-
resolveJson(valuesToEdit),
|
|
129
|
-
stripIndents`
|
|
130
|
-
Please fill in all secrets for:
|
|
131
|
-
|
|
132
|
-
${Object.entries(envAndComponents)
|
|
133
|
-
.map(
|
|
134
|
-
([componentName, envs]) => `- ${componentName}: ${envs.join(", ")}`,
|
|
135
|
-
)
|
|
136
|
-
.join("\n")}
|
|
137
|
-
|
|
138
|
-
`,
|
|
139
|
-
);
|
|
140
|
-
// check for errors
|
|
141
|
-
hasErrors = false;
|
|
142
|
-
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
143
|
-
for (const env of envs) {
|
|
144
|
-
const usedKeys = valuesToEdit[componentName][env]
|
|
145
|
-
? Object.keys(valuesToEdit[componentName][env])
|
|
146
|
-
: [];
|
|
147
|
-
// check whether newValues have the exact number of keys
|
|
148
|
-
const secretEnvVarKeys = await getSecretEnvVarKeysToConfigure(
|
|
149
|
-
env,
|
|
150
|
-
componentName,
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
const extranous = difference(usedKeys, secretEnvVarKeys);
|
|
154
|
-
const missing = difference(secretEnvVarKeys, usedKeys);
|
|
155
|
-
|
|
156
|
-
if (extranous.length > 0 || missing.length > 0) {
|
|
157
|
-
instance.log("");
|
|
158
|
-
instance.log(
|
|
159
|
-
`😿 Oh no! There is something wrong with "${componentName}"`,
|
|
160
|
-
);
|
|
161
|
-
instance.log("");
|
|
162
|
-
if (extranous.length > 0) {
|
|
163
|
-
instance.log("these secrets are not declared in the config");
|
|
164
|
-
extranous.forEach((key) => instance.log(key));
|
|
165
|
-
instance.log("");
|
|
166
|
-
}
|
|
167
|
-
if (missing.length > 0) {
|
|
168
|
-
instance.log("these secrets have not been provided:");
|
|
169
|
-
missing.forEach((key) => instance.log(key));
|
|
170
|
-
instance.log("");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
await delay(1000);
|
|
174
|
-
const shouldContinue = await instance.confirm("Try again? 🤔");
|
|
175
|
-
|
|
176
|
-
if (!shouldContinue) {
|
|
177
|
-
throw new Error("abort");
|
|
178
|
-
}
|
|
179
|
-
hasErrors = true;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const config = await getProjectConfig();
|
|
185
|
-
if (!config) {
|
|
186
|
-
throw new Error("no catladder config found");
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
instance.log("");
|
|
190
|
-
instance.log("writing all secrets to the vault, please wait...");
|
|
191
|
-
instance.log("");
|
|
192
|
-
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
193
|
-
for (const env of envs) {
|
|
194
|
-
instance.log("writing " + env + ":" + componentName + "...\n");
|
|
195
|
-
await (
|
|
196
|
-
await instance.getVaultManager()
|
|
197
|
-
).writeSecrets(env, componentName, valuesToEdit[componentName][env]);
|
|
198
|
-
instance.log("");
|
|
199
|
-
instance.log("✅ " + env + ":" + componentName);
|
|
200
|
-
instance.log("--------------------------------\n");
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
await mirrorSecretsToCiBackends(
|
|
205
|
-
instance,
|
|
206
|
-
config,
|
|
207
|
-
envAndComponents,
|
|
208
|
-
valuesToEdit,
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
instance.log("done! 😻");
|
|
212
|
-
instance.log("");
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* the vault is the source of truth; every enabled CI backend receives
|
|
217
|
-
* a mirrored copy of the edited secrets (gitlab variables, github
|
|
218
|
-
* secrets), so all CI systems stay in sync
|
|
219
|
-
*/
|
|
220
|
-
const mirrorSecretsToCiBackends = async (
|
|
221
|
-
instance: IO,
|
|
222
|
-
config: Config,
|
|
223
|
-
envAndComponents: { [componentName: string]: string[] },
|
|
224
|
-
valuesToEdit: Vars,
|
|
225
|
-
) => {
|
|
226
|
-
const enabled = getEnabledPipelineTypes(config);
|
|
227
|
-
|
|
228
|
-
// gitlab mirror (unless gitlab is the vault itself, then it is
|
|
229
|
-
// already written)
|
|
230
|
-
if (getVaultConfig(config).type !== "gitlab" && enabled.includes("gitlab")) {
|
|
231
|
-
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
232
|
-
for (const env of envs) {
|
|
233
|
-
await upsertAllVariables(
|
|
234
|
-
instance,
|
|
235
|
-
valuesToEdit[componentName][env],
|
|
236
|
-
env,
|
|
237
|
-
componentName,
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
instance.log("✅ mirrored to gitlab");
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (!enabled.includes("github")) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
const kinds = new Map<string, "secret" | "variable">();
|
|
248
|
-
for (const [componentName, envs] of Object.entries(envAndComponents)) {
|
|
249
|
-
for (const env of envs) {
|
|
250
|
-
const { secretEnvVarKeys, jobOnlyVars } = await getEnvironment(
|
|
251
|
-
env,
|
|
252
|
-
componentName,
|
|
253
|
-
);
|
|
254
|
-
[
|
|
255
|
-
...secretEnvVarKeys,
|
|
256
|
-
...jobOnlyVars.build.secretEnvVarKeys,
|
|
257
|
-
...jobOnlyVars.deploy.secretEnvVarKeys,
|
|
258
|
-
].forEach(({ key, kind }) =>
|
|
259
|
-
kinds.set(getSecretVarName(env, componentName, key), kind ?? "secret"),
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
const secrets = Object.entries(envAndComponents).flatMap(
|
|
264
|
-
([componentName, envs]) =>
|
|
265
|
-
envs.flatMap((env) =>
|
|
266
|
-
Object.entries(valuesToEdit[componentName][env] ?? {}).map(
|
|
267
|
-
([key, value]) => {
|
|
268
|
-
const name = getSecretVarName(env, componentName, key);
|
|
269
|
-
return {
|
|
270
|
-
name,
|
|
271
|
-
value:
|
|
272
|
-
typeof value === "string" ? value : JSON.stringify(value ?? ""),
|
|
273
|
-
kind: kinds.get(name) ?? ("secret" as const),
|
|
274
|
-
};
|
|
275
|
-
},
|
|
276
|
-
),
|
|
277
|
-
),
|
|
278
|
-
);
|
|
279
|
-
if (secrets.length === 0) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
if (!(await isGhAuthenticated())) {
|
|
283
|
-
instance.log(
|
|
284
|
-
"⚠️ github pipeline is enabled, but the github cli (gh) is not authenticated — run `project secrets-sync-github` later to mirror the secrets",
|
|
285
|
-
);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
const repo = await getConfiguredGithubRepo();
|
|
289
|
-
if (!repo) {
|
|
290
|
-
instance.log(
|
|
291
|
-
"⚠️ github pipeline is enabled, but no github repository was found — run `project secrets-sync-github` later to mirror the secrets",
|
|
292
|
-
);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
instance.log(`mirroring ${secrets.length} secrets to github ('${repo}')...`);
|
|
296
|
-
await pushSecretsToGithub(
|
|
297
|
-
instance,
|
|
298
|
-
repo,
|
|
299
|
-
secrets,
|
|
300
|
-
await collectSecretTargets(config),
|
|
301
|
-
);
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
export const projectConfigSecrets = async (io: IO, envComponent?: string) => {
|
|
305
|
-
if (!envComponent) {
|
|
306
|
-
const allEnvAndcomponents = await getAllComponentsWithAllEnvsHierarchical();
|
|
307
|
-
await doItFor(io, allEnvAndcomponents);
|
|
308
|
-
} else {
|
|
309
|
-
const { env, componentName } = parseChoice(envComponent);
|
|
310
|
-
|
|
311
|
-
// componentName can be null. in this case, iterate over all components
|
|
312
|
-
if (!componentName) {
|
|
313
|
-
const components = await getProjectComponents();
|
|
314
|
-
await doItFor(io, Object.fromEntries(components.map((c) => [c, [env]])));
|
|
315
|
-
}
|
|
316
|
-
if (componentName) {
|
|
317
|
-
await doItFor(io, {
|
|
318
|
-
[componentName]: [env],
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
};
|