mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
Handle idempotent secret removal retries
This commit is contained in:
1 parent
6bf5159d4f
commit
aff9865a06
2 files changed
+70
-12
No files matched your search
@@ -1127,6 +1127,58 @@ describeEmbeddedPostgres("secretService", () => {
|
||||
expect(recreated.id).not.toBe(secret.id);
|
||||
});
|
||||
|
||||
it("treats missing provider secrets as already removed during removal retry", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const svc = secretService(db);
|
||||
const externalRef =
|
||||
"arn:aws:secretsmanager:us-east-1:123456789012:secret:paperclip/prod-use1/company-1/retry-delete";
|
||||
const secretId = randomUUID();
|
||||
await db.insert(companySecrets).values({
|
||||
id: secretId,
|
||||
companyId,
|
||||
key: `retry-delete__deleted__${secretId}`,
|
||||
name: `Retry Delete__deleted__${secretId}`,
|
||||
provider: "aws_secrets_manager",
|
||||
managedMode: "paperclip_managed",
|
||||
externalRef,
|
||||
latestVersion: 1,
|
||||
status: "deleted",
|
||||
deletedAt: new Date(),
|
||||
});
|
||||
await db.insert(companySecretVersions).values({
|
||||
secretId,
|
||||
version: 1,
|
||||
material: {
|
||||
scheme: "aws_secrets_manager_v1",
|
||||
secretId: externalRef,
|
||||
versionId: "aws-version-1",
|
||||
source: "managed",
|
||||
},
|
||||
valueSha256: "value-sha-1",
|
||||
fingerprintSha256: "fingerprint-sha-1",
|
||||
providerVersionRef: "aws-version-1",
|
||||
status: "current",
|
||||
});
|
||||
const deleteSpy = vi.spyOn(awsSecretsManagerProvider, "deleteOrArchive").mockRejectedValueOnce(
|
||||
new SecretProviderClientError({
|
||||
code: "not_found",
|
||||
provider: "aws_secrets_manager",
|
||||
operation: "delete_secret",
|
||||
message: "Secret not found.",
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(svc.remove(secretId)).resolves.toMatchObject({ id: secretId });
|
||||
const persisted = await db
|
||||
.select()
|
||||
.from(companySecrets)
|
||||
.where(eq(companySecrets.id, secretId))
|
||||
.then((rows) => rows[0] ?? null);
|
||||
|
||||
expect(deleteSpy).toHaveBeenCalledTimes(1);
|
||||
expect(persisted).toBeNull();
|
||||
});
|
||||
|
||||
it("removes DB rows even when the attached provider vault is disabled", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const svc = secretService(db);
|
||||
|
||||
@@ -1880,18 +1880,24 @@ export function secretService(db: Db) {
|
||||
? toProviderVaultRuntimeConfig(providerConfig)
|
||||
: null;
|
||||
if (!secret.providerConfigId || providerRuntimeConfig) {
|
||||
await provider.deleteOrArchive({
|
||||
material: versionRow?.material as Record<string, unknown> | undefined,
|
||||
externalRef: secret.externalRef,
|
||||
providerConfig: providerRuntimeConfig,
|
||||
context: {
|
||||
companyId: secret.companyId,
|
||||
secretKey: secret.key,
|
||||
secretName: secret.name,
|
||||
version: secret.latestVersion,
|
||||
},
|
||||
mode: "delete",
|
||||
});
|
||||
try {
|
||||
await provider.deleteOrArchive({
|
||||
material: versionRow?.material as Record<string, unknown> | undefined,
|
||||
externalRef: secret.externalRef,
|
||||
providerConfig: providerRuntimeConfig,
|
||||
context: {
|
||||
companyId: secret.companyId,
|
||||
secretKey: secret.key,
|
||||
secretName: secret.name,
|
||||
version: secret.latestVersion,
|
||||
},
|
||||
mode: "delete",
|
||||
});
|
||||
} catch (error) {
|
||||
if (!isSecretProviderClientError(error) || error.code !== "not_found") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
await db.delete(companySecrets).where(eq(companySecrets.id, secretId));
|
||||
return secret;
|
||||
|
||||
Reference in new issue
Block a user