Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import {
GetRecommendationSummariesCommand,
RecommendationSummary,
} from '@aws-sdk/client-compute-optimizer';
import { getAwsClient, makeAwsRequest } from '@openops/common';
import {
getAwsClient,
isAwsPermissionError,
makeAwsRequest,
} from '@openops/common';
import { logger } from '@openops/server-shared';

export async function getRecommendationSummaries(
credentials: any,
Expand All @@ -12,21 +17,33 @@ export async function getRecommendationSummaries(
const results: RecommendationSummary[] = [];

for (const region of regions) {
const client = getComputeOptimizerClient(credentials, region);
const command = new GetRecommendationSummariesCommand({
nextToken: '',
});
const regionalResults = await makeAwsRequest(client, command);
try {
const client = getComputeOptimizerClient(credentials, region);
const command = new GetRecommendationSummariesCommand({
nextToken: '',
});
const regionalResults = await makeAwsRequest(client, command);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
for (const result of regionalResults as any) {
const recommendationSummaries = result.recommendationSummaries?.map(
(item: any) => ({ ...item, region }),
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
for (const result of regionalResults as any) {
const recommendationSummaries = result.recommendationSummaries?.map(
(item: any) => ({ ...item, region }),
);

if (recommendationSummaries) {
results.push(...recommendationSummaries);
if (recommendationSummaries) {
results.push(...recommendationSummaries);
}
}
} catch (error) {
if (isAwsPermissionError(error)) {
logger.debug('Skipping AWS region due to permission error', {
region,
error,
});
continue;
}

throw error;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EBSFinding } from '@aws-sdk/client-compute-optimizer';
import { groupARNsByRegion } from '@openops/common';
import { groupARNsByRegion, isAwsPermissionError } from '@openops/common';
import { logger } from '@openops/server-shared';
import { EbsRecommendationsBuilder } from './ebs-recommendations-builder';
import { ComputeOptimizerRecommendation } from './get-recommendations';

Expand All @@ -18,12 +19,24 @@ export async function getEbsRecommendationsForRegions(
);

for (const region of regions) {
const recommendations = await recommendationsBuilder.getRecommendations(
credentials,
region,
);
try {
const recommendations = await recommendationsBuilder.getRecommendations(
credentials,
region,
);

result.push(...recommendations);
result.push(...recommendations);
} catch (error) {
if (isAwsPermissionError(error)) {
logger.debug('Skipping AWS region due to permission error', {
region,
error,
});
continue;
}

throw error;
}
}

return result;
Expand All @@ -45,13 +58,25 @@ export async function getEbsRecommendationsForARNs(
);

for (const region in arnsPerRegion) {
const recommendations = await recommendationsBuilder.getRecommendations(
credentials,
region,
arnsPerRegion[region],
);
try {
const recommendations = await recommendationsBuilder.getRecommendations(
credentials,
region,
arnsPerRegion[region],
);

result.push(...recommendations);
} catch (error) {
if (isAwsPermissionError(error)) {
logger.debug('Skipping AWS region due to permission error', {
region,
error,
});
continue;
}

result.push(...recommendations);
throw error;
}
}

return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Finding } from '@aws-sdk/client-compute-optimizer';
import { groupARNsByRegion } from '@openops/common';
import { groupARNsByRegion, isAwsPermissionError } from '@openops/common';
import { logger } from '@openops/server-shared';
import { Ec2RecommendationsBuilder } from './ec2-recommendations-builder';
import { ComputeOptimizerRecommendation } from './get-recommendations';

Expand All @@ -18,12 +19,24 @@ export async function getEC2RecommendationsForRegions(
);

for (const region of regions) {
const recommendations = await recommendationsBuilder.getRecommendations(
credentials,
region,
);
try {
const recommendations = await recommendationsBuilder.getRecommendations(
credentials,
region,
);

result.push(...recommendations);
result.push(...recommendations);
} catch (error) {
if (isAwsPermissionError(error)) {
logger.debug('Skipping AWS region due to permission error', {
region,
error,
});
continue;
}

throw error;
}
}

return result;
Expand All @@ -46,13 +59,25 @@ export async function getEC2RecommendationsForARNs(
);

for (const region in arnsPerRegion) {
const recommendations = await recommendationsBuilder.getRecommendations(
credentials,
region,
arnsPerRegion[region],
);
try {
const recommendations = await recommendationsBuilder.getRecommendations(
credentials,
region,
arnsPerRegion[region],
);

result.push(...recommendations);
} catch (error) {
if (isAwsPermissionError(error)) {
logger.debug('Skipping AWS region due to permission error', {
region,
error,
});
continue;
}

result.push(...recommendations);
throw error;
}
}

return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
const debugMock = jest.fn();

jest.mock('@openops/server-shared', () => {
const actual = jest.requireActual('@openops/server-shared');

return {
...actual,
logger: {
...actual.logger,
debug: debugMock,
},
};
});

const CREDENTIALS = {
accessKeyId: 'some accessKeyId',
secretAccessKey: 'some secretAccessKey',
Expand Down Expand Up @@ -160,6 +174,41 @@ describe('Get recommendations summary', () => {
]);
expect(openopsCommonMock.getAwsClient).toHaveBeenCalledTimes(2);
});

test('should skip permission denied regions when fetching recommendation summaries', async () => {
const summary = createRecommendationsSummaryResponse([
{
recommendationResourceType: RecommendationSourceType.EBS_VOLUME,
},
]);

openopsCommonMock.makeAwsRequest
.mockRejectedValueOnce(new Error('AccessDenied'))
.mockResolvedValueOnce([summary]);

const recommendations: any[] = await getRecommendationSummaries(
CREDENTIALS,
['region1', 'region2'],
);

expect(recommendations).toHaveLength(1);
expect(recommendations[0].region).toBe('region2');
expect(debugMock).toHaveBeenCalledTimes(1);
});

test('should return empty array when every recommendation summary region is denied', async () => {
openopsCommonMock.makeAwsRequest
.mockRejectedValueOnce(new Error('AccessDenied'))
.mockRejectedValueOnce(new Error('UnauthorizedOperation'));

const recommendations = await getRecommendationSummaries(CREDENTIALS, [
'region1',
'region2',
]);

expect(recommendations).toEqual([]);
expect(debugMock).toHaveBeenCalledTimes(2);
});
});

function createRecommendationsSummaryResponse(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
const debugMock = jest.fn();

jest.mock('@openops/server-shared', () => {
const actual = jest.requireActual('@openops/server-shared');

return {
...actual,
logger: {
...actual.logger,
debug: debugMock,
},
};
});

const CREDENTIALS = {
accessKeyId: 'some accessKeyId',
secretAccessKey: 'some secretAccessKey',
Expand Down Expand Up @@ -156,6 +170,41 @@ describe('Get ebs volumes recommendations', () => {
expect(recommendations.length).toBe(0);
});

test('should skip permission denied regions for EBS recommendations', async () => {
const recommendationsInRegion = createRecommendationsResponse([
createRecommendations('arn:aws:ec2:us-east-1:123456789123:volume/vol-1'),
]);

sendMock
.mockRejectedValueOnce(new Error('AccessDenied'))
.mockResolvedValueOnce([recommendationsInRegion]);

const recommendations = await getEbsRecommendationsForRegions(
CREDENTIALS,
EBSFinding.OPTIMIZED,
['eu-west-1', 'us-east-1'],
);

expect(recommendations.map((result) => result.arn)).toEqual([
'arn:aws:ec2:us-east-1:123456789123:volume/vol-1',
]);
});

test('should return empty array when all EBS recommendation regions are denied', async () => {
sendMock
.mockRejectedValueOnce(new Error('AccessDenied'))
.mockRejectedValueOnce(new Error('UnauthorizedOperation'));

const recommendations = await getEbsRecommendationsForRegions(
CREDENTIALS,
EBSFinding.NOT_OPTIMIZED,
['eu-west-1', 'us-east-1'],
);

expect(recommendations).toEqual([]);
expect(debugMock).toHaveBeenCalledTimes(2);
});

test('should return all the Ebs Volumes Recommendations for the provided volumes', async () => {
const recommendationsInRegion1 = createRecommendationsResponse([
createRecommendations('arn:aws:ec2:us-east-1:123456789123:volume/vol-1'),
Expand Down Expand Up @@ -222,6 +271,31 @@ describe('Get ebs volumes recommendations', () => {
expect(recommendations.length).toBe(0);
});

test('should skip permission denied arn regions for EBS recommendations', async () => {
sendMock
.mockResolvedValueOnce([
createRecommendationsResponse([
createRecommendations(
'arn:aws:ec2:us-east-1:123456789123:volume/vol-1',
),
]),
])
.mockRejectedValueOnce(new Error('AccessDenied'));

const recommendations = await getEbsRecommendationsForARNs(
CREDENTIALS,
EBSFinding.OPTIMIZED,
[
'arn:aws:ec2:us-east-1:123456789123:volume/vol-1',
'arn:aws:ec2:us-east-2:123456789123:volume/vol-2',
],
);

expect(recommendations.map((result) => result.arn)).toEqual([
'arn:aws:ec2:us-east-1:123456789123:volume/vol-1',
]);
});

test('should return an empty array when the given volumes have 0 savings recommendations', async () => {
const findingType = EBSFinding.OPTIMIZED;
const recommendationZeroSaving = createRecommendations(
Expand Down
Loading
Loading