@occam-scaly/scaly-cli 0.2.5 → 0.2.6
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/lib/scaly-api.js +31 -0
- package/lib/scaly-artifacts.js +21 -0
- package/lib/scaly-plan.js +67 -0
- package/package.json +1 -1
package/lib/scaly-api.js
CHANGED
|
@@ -254,6 +254,27 @@ const QUERIES = {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
`,
|
|
257
|
+
listAddOnsByType: `
|
|
258
|
+
query ListAddOnsByType($accountId: String!, $type: AddOnTypeEnum!, $take: Int) {
|
|
259
|
+
listAddOns(
|
|
260
|
+
where: {
|
|
261
|
+
AND: [
|
|
262
|
+
{ accountId: { equals: $accountId } }
|
|
263
|
+
{ type: { equals: $type } }
|
|
264
|
+
{ isDeleted: { equals: false } }
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
take: $take
|
|
268
|
+
) {
|
|
269
|
+
id
|
|
270
|
+
name
|
|
271
|
+
type
|
|
272
|
+
accountId
|
|
273
|
+
status
|
|
274
|
+
addOnCognito { userPoolName }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
`,
|
|
257
278
|
listStacks: `
|
|
258
279
|
query ListStacks($take: Int) {
|
|
259
280
|
listStacks(
|
|
@@ -351,6 +372,15 @@ async function findAddOnByName({ name, accountId }) {
|
|
|
351
372
|
return data?.listAddOns || [];
|
|
352
373
|
}
|
|
353
374
|
|
|
375
|
+
async function listAddOnsByType({ accountId, type, take = 10 }) {
|
|
376
|
+
const data = await graphqlRequest(QUERIES.listAddOnsByType, {
|
|
377
|
+
accountId,
|
|
378
|
+
type,
|
|
379
|
+
take
|
|
380
|
+
});
|
|
381
|
+
return data?.listAddOns || [];
|
|
382
|
+
}
|
|
383
|
+
|
|
354
384
|
async function createStack({ accountId, name, size, minIdle }) {
|
|
355
385
|
const data = await graphqlRequest(MUTATIONS.createStack, {
|
|
356
386
|
data: {
|
|
@@ -419,6 +449,7 @@ module.exports = {
|
|
|
419
449
|
findStackByName,
|
|
420
450
|
findAppByName,
|
|
421
451
|
findAddOnByName,
|
|
452
|
+
listAddOnsByType,
|
|
422
453
|
listStacks,
|
|
423
454
|
listApps,
|
|
424
455
|
createStack,
|
package/lib/scaly-artifacts.js
CHANGED
|
@@ -34,6 +34,27 @@ const SENSITIVE_PATH_PATTERNS = [
|
|
|
34
34
|
// ssh / aws creds
|
|
35
35
|
/(^|\/)\.ssh(\/|$)/i,
|
|
36
36
|
/(^|\/)\.aws(\/|$)/i,
|
|
37
|
+
// common package registry credentials
|
|
38
|
+
/(^|\/)\.npmrc$/i,
|
|
39
|
+
/(^|\/)\.pypirc$/i,
|
|
40
|
+
// docker registry auth
|
|
41
|
+
/(^|\/)\.docker\/config\.json$/i,
|
|
42
|
+
// kubernetes creds
|
|
43
|
+
/(^|\/)\.kube(\/|$)/i,
|
|
44
|
+
/(^|\/)(kubeconfig|.*\.kubeconfig)$/i,
|
|
45
|
+
// terraform state (often contains secrets)
|
|
46
|
+
/(^|\/).*\.tfstate(\.backup)?$/i,
|
|
47
|
+
/(^|\/)terraform\.tfstate(\.backup)?$/i,
|
|
48
|
+
// GCP service account keys / generic credentials files
|
|
49
|
+
/(^|\/)credentials\.json$/i,
|
|
50
|
+
/(^|\/)service-account.*\.json$/i,
|
|
51
|
+
// keystores / pkcs
|
|
52
|
+
/\.p12$/i,
|
|
53
|
+
/\.pfx$/i,
|
|
54
|
+
/\.keystore$/i,
|
|
55
|
+
/\.jks$/i,
|
|
56
|
+
// generic "secrets" files (defense-in-depth; can be overridden)
|
|
57
|
+
/(^|\/).*secrets?.*\.(ya?ml|json)$/i,
|
|
37
58
|
// private keys
|
|
38
59
|
/\.pem$/i,
|
|
39
60
|
/\.key$/i,
|
package/lib/scaly-plan.js
CHANGED
|
@@ -122,6 +122,12 @@ async function buildPlan({ config, env, appName }) {
|
|
|
122
122
|
accountId = await resolveAccountId({ config });
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
const requestedAuthPools = new Set();
|
|
126
|
+
for (const a of filteredApps) {
|
|
127
|
+
const pool = a && a.auth && a.auth.userPool;
|
|
128
|
+
if (typeof pool === 'string' && pool.trim()) requestedAuthPools.add(pool);
|
|
129
|
+
}
|
|
130
|
+
|
|
125
131
|
let stackOpId = null;
|
|
126
132
|
if (!currentStack) {
|
|
127
133
|
stackOpId = 'op_stack_create';
|
|
@@ -258,6 +264,7 @@ async function buildPlan({ config, env, appName }) {
|
|
|
258
264
|
|
|
259
265
|
if (Array.isArray(config.addons) && config.addons.length) {
|
|
260
266
|
let addOnReadable = true;
|
|
267
|
+
let existingUserPools = null;
|
|
261
268
|
for (const addOn of config.addons) {
|
|
262
269
|
if (!addOn || typeof addOn !== 'object') continue;
|
|
263
270
|
const apiType = mapAddOnType(addOn.type);
|
|
@@ -274,6 +281,28 @@ async function buildPlan({ config, env, appName }) {
|
|
|
274
281
|
|
|
275
282
|
let currentAddOn = null;
|
|
276
283
|
try {
|
|
284
|
+
if (apiType === 'COGNITO') {
|
|
285
|
+
try {
|
|
286
|
+
existingUserPools =
|
|
287
|
+
existingUserPools ||
|
|
288
|
+
(await api.listAddOnsByType({
|
|
289
|
+
accountId,
|
|
290
|
+
type: 'COGNITO',
|
|
291
|
+
take: 10
|
|
292
|
+
}));
|
|
293
|
+
} catch {
|
|
294
|
+
existingUserPools = null;
|
|
295
|
+
}
|
|
296
|
+
if (
|
|
297
|
+
Array.isArray(existingUserPools) &&
|
|
298
|
+
existingUserPools.length > 1
|
|
299
|
+
) {
|
|
300
|
+
warnings.push(
|
|
301
|
+
`Multiple user groups add-ons exist in this account (${existingUserPools.length}). Scaly typically expects one user pool per account.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
277
306
|
const list = await api.findAddOnByName({ name: addOn.name, accountId });
|
|
278
307
|
currentAddOn = list?.[0] || null;
|
|
279
308
|
if (list.length > 1) {
|
|
@@ -291,6 +320,44 @@ async function buildPlan({ config, env, appName }) {
|
|
|
291
320
|
}
|
|
292
321
|
|
|
293
322
|
if (!currentAddOn) {
|
|
323
|
+
if (apiType === 'COGNITO') {
|
|
324
|
+
const allowMultiple =
|
|
325
|
+
addOn.allow_multiple_user_pools === true ||
|
|
326
|
+
addOn.allowMultipleUserPools === true ||
|
|
327
|
+
config?.account?.allow_multiple_user_pools === true ||
|
|
328
|
+
config?.account?.allowMultipleUserPools === true;
|
|
329
|
+
|
|
330
|
+
const existing =
|
|
331
|
+
Array.isArray(existingUserPools) && existingUserPools.length
|
|
332
|
+
? existingUserPools[0]
|
|
333
|
+
: null;
|
|
334
|
+
|
|
335
|
+
if (existing && !allowMultiple) {
|
|
336
|
+
warnings.push(
|
|
337
|
+
`User groups add-on '${addOn.name}' would create a new user pool, but this account already has '${existing.name}'. Reusing the existing pool is recommended to avoid "missing users" surprises. To create multiple pools, set addons[].allow_multiple_user_pools: true.`
|
|
338
|
+
);
|
|
339
|
+
if (requestedAuthPools.has(addOn.name)) {
|
|
340
|
+
warnings.push(
|
|
341
|
+
`app.auth.userPool is set to '${addOn.name}'. Consider changing it to '${existing.name}' to keep existing users.`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
ops.push({
|
|
345
|
+
id: `op_addon_noop_${existing.name}`,
|
|
346
|
+
kind: 'addon',
|
|
347
|
+
action: 'noop',
|
|
348
|
+
resource: {
|
|
349
|
+
type: 'addon',
|
|
350
|
+
name: existing.name,
|
|
351
|
+
id: existing.id
|
|
352
|
+
},
|
|
353
|
+
current: pick(existing, ['id', 'name', 'type', 'accountId']),
|
|
354
|
+
desired: { name: existing.name, type: apiType, accountId },
|
|
355
|
+
diff: []
|
|
356
|
+
});
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
294
361
|
const desired = { name: addOn.name, type: apiType, accountId };
|
|
295
362
|
if (apiType === 'DATABASE') {
|
|
296
363
|
const dbType = mapDbEngine(addOn.engine);
|