mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-29 22:02:57 +00:00
Merge pull request #1735 from akhilmhdh/import/recursive
feat(server): recursive imported secret fetch for api
This commit is contained in:
backend/src/services
@ -1,33 +1,66 @@
|
||||
import { SecretType, TSecretImports } from "@app/db/schemas";
|
||||
import { SecretType, TSecretImports, TSecrets } from "@app/db/schemas";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
|
||||
import { TSecretDALFactory } from "../secret/secret-dal";
|
||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||
import { TSecretImportDALFactory } from "./secret-import-dal";
|
||||
|
||||
type TSecretImportSecrets = {
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
environmentInfo: {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
};
|
||||
folderId: string | undefined;
|
||||
importFolderId: string;
|
||||
secrets: (TSecrets & { workspace: string; environment: string; _id: string })[];
|
||||
};
|
||||
|
||||
const LEVEL_BREAK = 10;
|
||||
const getImportUniqKey = (envSlug: string, path: string) => `${envSlug}=${path}`;
|
||||
export const fnSecretsFromImports = async ({
|
||||
allowedImports,
|
||||
allowedImports: possibleCyclicImports,
|
||||
folderDAL,
|
||||
secretDAL
|
||||
secretDAL,
|
||||
secretImportDAL,
|
||||
depth = 0,
|
||||
cyclicDetector = new Set()
|
||||
}: {
|
||||
allowedImports: (Omit<TSecretImports, "importEnv"> & {
|
||||
importEnv: { id: string; slug: string; name: string };
|
||||
})[];
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findByManySecretPath">;
|
||||
secretDAL: Pick<TSecretDALFactory, "find">;
|
||||
secretImportDAL: Pick<TSecretImportDALFactory, "findByFolderIds">;
|
||||
depth?: number;
|
||||
cyclicDetector?: Set<string>;
|
||||
}) => {
|
||||
const importedFolders = await folderDAL.findByManySecretPath(
|
||||
allowedImports.map(({ importEnv, importPath }) => ({
|
||||
envId: importEnv.id,
|
||||
secretPath: importPath
|
||||
}))
|
||||
// avoid going more than a depth
|
||||
if (depth >= LEVEL_BREAK) return [];
|
||||
|
||||
const allowedImports = possibleCyclicImports.filter(
|
||||
({ importPath, importEnv }) => !cyclicDetector.has(getImportUniqKey(importEnv.slug, importPath))
|
||||
);
|
||||
const folderIds = importedFolders.map((el) => el?.id).filter(Boolean) as string[];
|
||||
if (!folderIds.length) {
|
||||
|
||||
const importedFolders = (
|
||||
await folderDAL.findByManySecretPath(
|
||||
allowedImports.map(({ importEnv, importPath }) => ({
|
||||
envId: importEnv.id,
|
||||
secretPath: importPath
|
||||
}))
|
||||
)
|
||||
).filter(Boolean); // remove undefined ones
|
||||
if (!importedFolders.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const importedFolderIds = importedFolders.map((el) => el?.id) as string[];
|
||||
const importedFolderGroupBySourceImport = groupBy(importedFolders, (i) => `${i?.envId}-${i?.path}`);
|
||||
const importedSecrets = await secretDAL.find(
|
||||
{
|
||||
$in: { folderId: folderIds },
|
||||
$in: { folderId: importedFolderIds },
|
||||
type: SecretType.Shared
|
||||
},
|
||||
{
|
||||
@ -35,18 +68,50 @@ export const fnSecretsFromImports = async ({
|
||||
}
|
||||
);
|
||||
|
||||
const importedSecsGroupByFolderId = groupBy(importedSecrets, (i) => i.folderId);
|
||||
return allowedImports.map(({ importPath, importEnv }, i) => ({
|
||||
secretPath: importPath,
|
||||
environment: importEnv.slug,
|
||||
environmentInfo: importEnv,
|
||||
folderId: importedFolders?.[i]?.id,
|
||||
// this will ensure for cases when secrets are empty. Could be due to missing folder for a path or when emtpy secrets inside a given path
|
||||
secrets: (importedSecsGroupByFolderId?.[importedFolders?.[i]?.id as string] || []).map((item) => ({
|
||||
...item,
|
||||
const importedSecretsGroupByFolderId = groupBy(importedSecrets, (i) => i.folderId);
|
||||
|
||||
allowedImports.forEach(({ importPath, importEnv }) => {
|
||||
cyclicDetector.add(getImportUniqKey(importEnv.slug, importPath));
|
||||
});
|
||||
// now we need to check recursively deeper imports made inside other imports
|
||||
// we go level wise meaning we take all imports of a tree level and then go deeper ones level by level
|
||||
const deeperImports = await secretImportDAL.findByFolderIds(importedFolderIds);
|
||||
let secretsFromDeeperImports: TSecretImportSecrets[] = [];
|
||||
if (deeperImports.length) {
|
||||
secretsFromDeeperImports = await fnSecretsFromImports({
|
||||
allowedImports: deeperImports,
|
||||
secretImportDAL,
|
||||
folderDAL,
|
||||
secretDAL,
|
||||
depth: depth + 1,
|
||||
cyclicDetector
|
||||
});
|
||||
}
|
||||
const secretsFromdeeperImportGroupedByFolderId = groupBy(secretsFromDeeperImports, (i) => i.importFolderId);
|
||||
|
||||
const secrets = allowedImports.map(({ importPath, importEnv, id, folderId }, i) => {
|
||||
const sourceImportFolder = importedFolderGroupBySourceImport[`${importEnv.id}-${importPath}`][0];
|
||||
const folderDeeperImportSecrets =
|
||||
secretsFromdeeperImportGroupedByFolderId?.[sourceImportFolder?.id || ""]?.[0]?.secrets || [];
|
||||
|
||||
return {
|
||||
secretPath: importPath,
|
||||
environment: importEnv.slug,
|
||||
workspace: "", // This field should not be used, it's only here to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||
_id: item.id // The old Python SDK depends on the _id field being returned. We return this to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||
}))
|
||||
}));
|
||||
environmentInfo: importEnv,
|
||||
folderId: importedFolders?.[i]?.id,
|
||||
id,
|
||||
importFolderId: folderId,
|
||||
// this will ensure for cases when secrets are empty. Could be due to missing folder for a path or when emtpy secrets inside a given path
|
||||
secrets: (importedSecretsGroupByFolderId?.[importedFolders?.[i]?.id as string] || [])
|
||||
.map((item) => ({
|
||||
...item,
|
||||
environment: importEnv.slug,
|
||||
workspace: "", // This field should not be used, it's only here to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||
_id: item.id // The old Python SDK depends on the _id field being returned. We return this to keep the older Python SDK versions backwards compatible with the new Postgres backend.
|
||||
}))
|
||||
.concat(folderDeeperImportSecrets)
|
||||
};
|
||||
});
|
||||
|
||||
return secrets;
|
||||
};
|
||||
|
@ -290,7 +290,7 @@ export const secretImportServiceFactory = ({
|
||||
})
|
||||
)
|
||||
);
|
||||
return fnSecretsFromImports({ allowedImports, folderDAL, secretDAL });
|
||||
return fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL });
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -525,7 +525,8 @@ export const secretServiceFactory = ({
|
||||
const importedSecrets = await fnSecretsFromImports({
|
||||
allowedImports,
|
||||
secretDAL,
|
||||
folderDAL
|
||||
folderDAL,
|
||||
secretImportDAL
|
||||
});
|
||||
|
||||
return {
|
||||
@ -630,7 +631,8 @@ export const secretServiceFactory = ({
|
||||
const importedSecrets = await fnSecretsFromImports({
|
||||
allowedImports,
|
||||
secretDAL,
|
||||
folderDAL
|
||||
folderDAL,
|
||||
secretImportDAL
|
||||
});
|
||||
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
||||
for (let j = 0; j < importedSecrets[i].secrets.length; j += 1) {
|
||||
|
Reference in New Issue
Block a user