Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions e2e-tests/utils/credential-provider-cleanup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import type { Logger } from './logger';
import {
BedrockAgentCoreControlClient,
DeleteApiKeyCredentialProviderCommand,
DeleteOauth2CredentialProviderCommand,
ListApiKeyCredentialProvidersCommand,
ListOauth2CredentialProvidersCommand,
} from '@aws-sdk/client-bedrock-agentcore-control';

export async function deleteCredentialProvider(
Expand All @@ -19,6 +21,31 @@ export async function deleteCredentialProvider(
}
}

export async function deleteOAuth2CredentialProvider(
client: BedrockAgentCoreControlClient,
logger: Logger,
name: string
): Promise<void> {
try {
await client.send(new DeleteOauth2CredentialProviderCommand({ name }));
logger.info(`Deleted OAuth2 credential provider: ${name}`);
} catch (error) {
const err = error as Error;
logger.warn(`Failed to delete OAuth2 credential provider ${name}: ${err.name}:${err.message}`);
}
}

/**
* Delete stale credential providers matching `prefix` and older than `minAgeMs`.
*
* Reaps BOTH provider types the e2e suite creates: API key providers and OAuth2 providers.
* These are distinct resource types with separate List/Delete APIs — an OAuth2 provider does
* NOT appear in ListApiKeyCredentialProviders — so both lists must be walked. The CUSTOM_JWT
* harness test registers a managed OAuth2 provider (`<name>-oauth`) created outside the
* CloudFormation stack, so teardown leaves it behind; without reaping it here, they accumulate
* against the account's 50-provider OAuth2 quota (L-431051DC) until every CUSTOM_JWT deploy
* fails with a limit-reached error.
*/
export async function cleanupStaleCredentialProviders(
client: BedrockAgentCoreControlClient,
logger: Logger,
Expand All @@ -28,15 +55,22 @@ export async function cleanupStaleCredentialProviders(
}
): Promise<void> {
const cutoff = new Date(Date.now() - options.minAgeMs);
const isStale = (p: { name?: string; createdTime?: Date }): boolean =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the helper function is called isStale but includes prefix matching logic which feels unrelated.

!!p.name?.startsWith(options.prefix) && !!p.createdTime && p.createdTime < cutoff;

let nextToken: string | undefined;
let apiKeyNextToken: string | undefined;
do {
const response = await client.send(new ListApiKeyCredentialProvidersCommand({ nextToken }));
const providers = response.credentialProviders ?? [];
const stale = providers.filter(p => p.name?.startsWith(options.prefix) && p.createdTime && p.createdTime < cutoff);

const response = await client.send(new ListApiKeyCredentialProvidersCommand({ nextToken: apiKeyNextToken }));
const stale = (response.credentialProviders ?? []).filter(isStale);
await Promise.all(stale.map(p => deleteCredentialProvider(client, logger, p.name!)));
apiKeyNextToken = response.nextToken;
} while (apiKeyNextToken);

nextToken = response.nextToken;
} while (nextToken);
let oauth2NextToken: string | undefined;
do {
const response = await client.send(new ListOauth2CredentialProvidersCommand({ nextToken: oauth2NextToken }));
const stale = (response.credentialProviders ?? []).filter(isStale);
await Promise.all(stale.map(p => deleteOAuth2CredentialProvider(client, logger, p.name!)));
oauth2NextToken = response.nextToken;
} while (oauth2NextToken);
}
Loading