feat(api): filter secrets by metadata

This commit is contained in:
Daniel Hougaard
2025-01-28 23:29:02 +01:00
parent 70515a1ca2
commit ea28c374a7
6 changed files with 83 additions and 1 deletions
+3 -1
View File
@@ -688,7 +688,9 @@ export const RAW_SECRETS = {
environment: "The slug of the environment to list secrets from.", environment: "The slug of the environment to list secrets from.",
secretPath: "The secret path to list secrets from.", secretPath: "The secret path to list secrets from.",
includeImports: "Weather to include imported secrets or not.", includeImports: "Weather to include imported secrets or not.",
tagSlugs: "The comma separated tag slugs to filter secrets." tagSlugs: "The comma separated tag slugs to filter secrets.",
secretMetadata:
"The secret metadata key-value pairs to filter secrets by. When querying for multiple metadata pairs, the query is treated as an AND operation. Secret metadata format is key1:value1,key2:value2."
}, },
CREATE: { CREATE: {
secretName: "The name of the secret to create.", secretName: "The name of the secret to create.",
@@ -181,6 +181,40 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
} }
], ],
querystring: z.object({ querystring: z.object({
secretMetadata: z
.string()
.optional()
.transform((value) => {
if (!value) return undefined;
const metadata = value.split(",").map((el) => {
const [key, val] = el.split(":");
return { key, value: val };
});
return metadata;
})
.superRefine((el, ctx) => {
if (el && !Array.isArray(el)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Invalid secretMetadata format. Correct format is key1:value1,key2:value2"
});
}
if (el) {
for (const item of el) {
if (!item.key || !item.value) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"Invalid secretMetadata format, key or value is missing. Correct format is key1:value1,key2:value2"
});
}
}
}
})
.describe(RAW_SECRETS.LIST.secretMetadata),
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId), workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId),
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug), workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment), environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
@@ -281,6 +315,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
projectId: workspaceId, projectId: workspaceId,
path: secretPath, path: secretPath,
secretMetadata: req.query.secretMetadata,
includeImports: req.query.include_imports, includeImports: req.query.include_imports,
recursive: req.query.recursive, recursive: req.query.recursive,
tagSlugs: req.query.tagSlugs tagSlugs: req.query.tagSlugs
@@ -414,6 +414,22 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`) .leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.where((bd) => {
if (filters?.secretMetadata && filters.secretMetadata?.length > 0) {
filters.secretMetadata.forEach((meta) => {
void bd.whereExists((qb) => {
void qb
.select("secretId")
.from(TableName.ResourceMetadata)
.whereRaw(`"${TableName.ResourceMetadata}"."secretId" = "${TableName.SecretV2}"."id"`)
.where({
[`${TableName.ResourceMetadata}.key` as string]: meta.key,
[`${TableName.ResourceMetadata}.value` as string]: meta.value
});
});
});
}
})
.select( .select(
selectAllTableCols(TableName.SecretV2), selectAllTableCols(TableName.SecretV2),
db.raw( db.raw(
@@ -481,6 +497,19 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
} }
] ]
}); });
// if (secretMetadata) {
// return data.filter((s) => {
// if (!s.secretMetadata.length) return false;
// return secretMetadata.every((m) => {
// const secretMeta = s.secretMetadata.find((sm) => sm.key === m.key);
// if (!secretMeta) return false;
// return secretMeta.value === m.value;
// });
// });
// }
return data; return data;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "get all secret" }); throw new DatabaseError({ error, name: "get all secret" });
@@ -30,6 +30,10 @@ export type TGetSecretsDTO = {
includeImports?: boolean; includeImports?: boolean;
recursive?: boolean; recursive?: boolean;
tagSlugs?: string[]; tagSlugs?: string[];
secretMetadata?: {
key: string;
value: string;
}[];
orderBy?: SecretsOrderBy; orderBy?: SecretsOrderBy;
orderDirection?: OrderByDirection; orderDirection?: OrderByDirection;
offset?: number; offset?: number;
@@ -310,6 +314,7 @@ export type TFindSecretsByFolderIdsFilter = {
orderDirection?: OrderByDirection; orderDirection?: OrderByDirection;
search?: string; search?: string;
tagSlugs?: string[]; tagSlugs?: string[];
secretMetadata?: { key: string; value: string }[];
includeTagsInSearch?: boolean; includeTagsInSearch?: boolean;
keys?: string[]; keys?: string[];
}; };
@@ -1263,6 +1263,13 @@ export const secretServiceFactory = ({
name: "bot_not_found_error" name: "bot_not_found_error"
}); });
if (paramsV2.secretMetadata) {
throw new BadRequestError({
message: "Please upgrade your project to filter secrets by metadata",
name: "SecretMetadataNotSupported"
});
}
const { secrets, imports } = await getSecrets({ const { secrets, imports } = await getSecrets({
actorId, actorId,
projectId, projectId,
@@ -182,6 +182,10 @@ export type TGetSecretsRawDTO = {
includeImports?: boolean; includeImports?: boolean;
recursive?: boolean; recursive?: boolean;
tagSlugs?: string[]; tagSlugs?: string[];
secretMetadata?: {
key: string;
value: string;
}[];
orderBy?: SecretsOrderBy; orderBy?: SecretsOrderBy;
orderDirection?: OrderByDirection; orderDirection?: OrderByDirection;
offset?: number; offset?: number;