|
| 1 | +import { getCredentialProvider } from './account'; |
| 2 | +import { |
| 3 | + ApplicationSignalsClient, |
| 4 | + StartDiscoveryCommand, |
| 5 | +} from '@aws-sdk/client-application-signals'; |
| 6 | +import { GetGroupsCommand, XRayClient } from '@aws-sdk/client-xray'; |
| 7 | + |
| 8 | +export interface TransactionSearchStatus { |
| 9 | + enabled: boolean; |
| 10 | + error?: string; |
| 11 | +} |
| 12 | + |
| 13 | +export interface TransactionSearchEnableResult { |
| 14 | + success: boolean; |
| 15 | + error?: string; |
| 16 | +} |
| 17 | + |
| 18 | +/** |
| 19 | + * Check if CloudWatch Application Signals (which powers transaction search) is enabled |
| 20 | + * by attempting to list X-Ray groups. If X-Ray is accessible, the account has tracing active. |
| 21 | + * We also try StartDiscovery as an idempotent check — if it succeeds, it was either already |
| 22 | + * enabled or is now enabled. |
| 23 | + */ |
| 24 | +export async function checkTransactionSearchEnabled(region: string): Promise<TransactionSearchStatus> { |
| 25 | + try { |
| 26 | + const xrayClient = new XRayClient({ |
| 27 | + region, |
| 28 | + credentials: getCredentialProvider(), |
| 29 | + }); |
| 30 | + await xrayClient.send(new GetGroupsCommand({})); |
| 31 | + return { enabled: true }; |
| 32 | + } catch (err: unknown) { |
| 33 | + const code = (err as { name?: string })?.name; |
| 34 | + if (code === 'AccessDeniedException' || code === 'AccessDenied') { |
| 35 | + return { enabled: false, error: 'Insufficient permissions to check X-Ray status' }; |
| 36 | + } |
| 37 | + // If the call fails for other reasons, assume not enabled / unknown |
| 38 | + return { enabled: false }; |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +/** |
| 43 | + * Enable CloudWatch Application Signals by calling StartDiscovery. |
| 44 | + * This creates the AWSServiceRoleForCloudWatchApplicationSignals service-linked role |
| 45 | + * and enables transaction search in the CloudWatch console. |
| 46 | + * |
| 47 | + * This is an idempotent operation — calling it when already enabled is a no-op. |
| 48 | + */ |
| 49 | +export async function enableTransactionSearch(region: string): Promise<TransactionSearchEnableResult> { |
| 50 | + try { |
| 51 | + const client = new ApplicationSignalsClient({ |
| 52 | + region, |
| 53 | + credentials: getCredentialProvider(), |
| 54 | + }); |
| 55 | + await client.send(new StartDiscoveryCommand({})); |
| 56 | + return { success: true }; |
| 57 | + } catch (err: unknown) { |
| 58 | + const code = (err as { name?: string })?.name; |
| 59 | + const message = (err as { message?: string })?.message ?? 'Unknown error'; |
| 60 | + |
| 61 | + if (code === 'AccessDeniedException' || code === 'AccessDenied') { |
| 62 | + return { |
| 63 | + success: false, |
| 64 | + error: `Insufficient IAM permissions to enable Application Signals. Required: application-signals:StartDiscovery. ${message}`, |
| 65 | + }; |
| 66 | + } |
| 67 | + return { success: false, error: `Failed to enable Application Signals: ${message}` }; |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +/** |
| 72 | + * Build a deep-link URL to the CloudWatch Transaction Search console page. |
| 73 | + */ |
| 74 | +export function buildTransactionSearchConsoleUrl(region: string): string { |
| 75 | + return `https://${region}.console.aws.amazon.com/cloudwatch/home?region=${region}#xray:traces/query`; |
| 76 | +} |
0 commit comments