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

View File

@@ -688,7 +688,9 @@ export const RAW_SECRETS = {
environment: "The slug of the environment to list secrets from.",
secretPath: "The secret path to list secrets from.",
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: {
secretName: "The name of the secret to create.",

View File

@@ -181,6 +181,40 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
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),
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
@@ -281,6 +315,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
actorAuthMethod: req.permission.authMethod,
projectId: workspaceId,
path: secretPath,
secretMetadata: req.query.secretMetadata,
includeImports: req.query.include_imports,
recursive: req.query.recursive,
tagSlugs: req.query.tagSlugs

View File

@@ -414,6 +414,22 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretTag}.id`
)
.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(
selectAllTableCols(TableName.SecretV2),
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;
} catch (error) {
throw new DatabaseError({ error, name: "get all secret" });

View File

@@ -30,6 +30,10 @@ export type TGetSecretsDTO = {
includeImports?: boolean;
recursive?: boolean;
tagSlugs?: string[];
secretMetadata?: {
key: string;
value: string;
}[];
orderBy?: SecretsOrderBy;
orderDirection?: OrderByDirection;
offset?: number;
@@ -310,6 +314,7 @@ export type TFindSecretsByFolderIdsFilter = {
orderDirection?: OrderByDirection;
search?: string;
tagSlugs?: string[];
secretMetadata?: { key: string; value: string }[];
includeTagsInSearch?: boolean;
keys?: string[];
};

View File

@@ -1263,6 +1263,13 @@ export const secretServiceFactory = ({
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({
actorId,
projectId,

View File

@@ -182,6 +182,10 @@ export type TGetSecretsRawDTO = {
includeImports?: boolean;
recursive?: boolean;
tagSlugs?: string[];
secretMetadata?: {
key: string;
value: string;
}[];
orderBy?: SecretsOrderBy;
orderDirection?: OrderByDirection;
offset?: number;