Skip to content
Merged
Show file tree
Hide file tree
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
14 changes: 13 additions & 1 deletion src/cli/aws/__tests__/transaction-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe('enableTransactionSearch', () => {
});
});

it('sets indexing to 100% on Default rule', async () => {
it('sets indexing to 100% on Default rule by default', async () => {
setupAllSuccess();

await enableTransactionSearch('us-east-1', '123456789012');
Expand All @@ -142,6 +142,18 @@ describe('enableTransactionSearch', () => {
});
});

it('sets indexing to custom percentage when provided', async () => {
setupAllSuccess();

await enableTransactionSearch('us-east-1', '123456789012', 50);

const lastXRayCall = mockXRaySend.mock.calls[mockXRaySend.mock.calls.length - 1]![0];
expect(lastXRayCall.input).toEqual({
Name: 'Default',
Rule: { Probabilistic: { DesiredSamplingPercentage: 50 } },
});
});

describe('error handling', () => {
it('returns error when Application Signals fails with AccessDeniedException', async () => {
const error = new Error('Not authorized');
Expand Down
26 changes: 12 additions & 14 deletions src/cli/aws/transaction-search.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getErrorMessage, isAccessDeniedError } from '../errors';
import { getCredentialProvider } from './account';
import { ApplicationSignalsClient, StartDiscoveryCommand } from '@aws-sdk/client-application-signals';
import {
Expand Down Expand Up @@ -30,7 +31,8 @@ const RESOURCE_POLICY_NAME = 'TransactionSearchXRayAccess';
*/
export async function enableTransactionSearch(
region: string,
accountId: string
accountId: string,
indexPercentage = 100
): Promise<TransactionSearchEnableResult> {
const credentials = getCredentialProvider();

Expand All @@ -39,9 +41,8 @@ export async function enableTransactionSearch(
const appSignalsClient = new ApplicationSignalsClient({ region, credentials });
await appSignalsClient.send(new StartDiscoveryCommand({}));
} catch (err: unknown) {
const code = (err as { name?: string })?.name;
const message = (err as { message?: string })?.message ?? 'Unknown error';
if (code === 'AccessDeniedException' || code === 'AccessDenied') {
const message = getErrorMessage(err);
if (isAccessDeniedError(err)) {
return { success: false, error: `Insufficient permissions to enable Application Signals: ${message}` };
}
return { success: false, error: `Failed to enable Application Signals: ${message}` };
Expand Down Expand Up @@ -76,9 +77,8 @@ export async function enableTransactionSearch(
await logsClient.send(new PutResourcePolicyCommand({ policyName: RESOURCE_POLICY_NAME, policyDocument }));
}
} catch (err: unknown) {
const code = (err as { name?: string })?.name;
const message = (err as { message?: string })?.message ?? 'Unknown error';
if (code === 'AccessDeniedException' || code === 'AccessDenied') {
const message = getErrorMessage(err);
if (isAccessDeniedError(err)) {
return { success: false, error: `Insufficient permissions to configure CloudWatch Logs policy: ${message}` };
}
return { success: false, error: `Failed to configure CloudWatch Logs policy: ${message}` };
Expand All @@ -93,9 +93,8 @@ export async function enableTransactionSearch(
await xrayClient.send(new UpdateTraceSegmentDestinationCommand({ Destination: 'CloudWatchLogs' }));
}
} catch (err: unknown) {
const code = (err as { name?: string })?.name;
const message = (err as { message?: string })?.message ?? 'Unknown error';
if (code === 'AccessDeniedException' || code === 'AccessDenied') {
const message = getErrorMessage(err);
if (isAccessDeniedError(err)) {
return { success: false, error: `Insufficient permissions to configure trace destination: ${message}` };
}
return { success: false, error: `Failed to configure trace destination: ${message}` };
Expand All @@ -106,13 +105,12 @@ export async function enableTransactionSearch(
await xrayClient.send(
new UpdateIndexingRuleCommand({
Name: 'Default',
Rule: { Probabilistic: { DesiredSamplingPercentage: 100 } },
Rule: { Probabilistic: { DesiredSamplingPercentage: indexPercentage } },
})
);
} catch (err: unknown) {
const code = (err as { name?: string })?.name;
const message = (err as { message?: string })?.message ?? 'Unknown error';
if (code === 'AccessDeniedException' || code === 'AccessDenied') {
const message = getErrorMessage(err);
if (isAccessDeniedError(err)) {
return { success: false, error: `Insufficient permissions to configure indexing rules: ${message}` };
}
return { success: false, error: `Failed to configure indexing rules: ${message}` };
Expand Down
12 changes: 12 additions & 0 deletions src/cli/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ export function getErrorMessage(err: unknown): string {
return err instanceof Error ? err.message : String(err);
}

/**
* Checks if an error is an AWS access denied error.
* Returns true for AccessDeniedException or AccessDenied error codes.
*/
export function isAccessDeniedError(err: unknown): boolean {
if (!err || typeof err !== 'object') {
return false;
}
const name = (err as { name?: string }).name;
return name === 'AccessDeniedException' || name === 'AccessDenied';
}

/**
* AWS error codes that indicate expired or invalid credentials.
* These errors can be recovered by re-authenticating.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,27 @@ describe('setupTransactionSearch', () => {
mockEnableTransactionSearch.mockResolvedValue({ success: true });
});

it('calls enableTransactionSearch with region and accountId and returns success', async () => {
it('calls enableTransactionSearch with region, accountId, and default 100% indexing', async () => {
const result = await setupTransactionSearch({
region: 'us-west-2',
accountId: '111222333444',
agentNames: ['my-agent'],
});

expect(mockEnableTransactionSearch).toHaveBeenCalledWith('us-west-2', '111222333444');
expect(mockEnableTransactionSearch).toHaveBeenCalledWith('us-west-2', '111222333444', 100);
expect(result).toEqual({ success: true });
});

it('passes custom transactionSearchIndexPercentage from config', async () => {
mockReadCliConfig.mockReturnValue({ transactionSearchIndexPercentage: 25 });

const result = await setupTransactionSearch({
region: 'us-east-1',
accountId: '123456789012',
agentNames: ['agent-1'],
});

expect(mockEnableTransactionSearch).toHaveBeenCalledWith('us-east-1', '123456789012', 25);
expect(result).toEqual({ success: true });
});

Expand Down
3 changes: 2 additions & 1 deletion src/cli/operations/deploy/post-deploy-observability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export async function setupTransactionSearch(
return { success: true };
}

const result = await enableTransactionSearch(region, accountId);
const indexPercentage = config.transactionSearchIndexPercentage ?? 100;
const result = await enableTransactionSearch(region, accountId, indexPercentage);

if (!result.success) {
return { success: false, error: result.error };
Expand Down
7 changes: 7 additions & 0 deletions src/lib/schemas/io/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface CliConfig {
uvDefaultIndex?: string;
uvIndex?: string;
disableTransactionSearch?: boolean;
transactionSearchIndexPercentage?: number;
}

/**
Expand All @@ -22,6 +23,12 @@ export function readCliConfig(): CliConfig {
if (typeof parsed.uvDefaultIndex === 'string') config.uvDefaultIndex = parsed.uvDefaultIndex;
if (typeof parsed.uvIndex === 'string') config.uvIndex = parsed.uvIndex;
if (parsed.disableTransactionSearch === true) config.disableTransactionSearch = true;
if (typeof parsed.transactionSearchIndexPercentage === 'number') {
const pct = parsed.transactionSearchIndexPercentage;
if (pct >= 0 && pct <= 100) {
config.transactionSearchIndexPercentage = pct;
}
}
return config;
} catch {
return {};
Expand Down
Loading