Skip to content

Commit

Permalink
fix: aws-sdk-js-v3, esbuild, misc (#669)
Browse files Browse the repository at this point in the history
* fix: [wip] replace clients

* fix: isAWSError update, remove aws-sdk v2 from deps

* fix: replace webpack with esbuild

- replace webpack with esbuild

* fix: tests, misc

- add defaultSemverRangePrefix to yarnrc
- getEc2Info with aws credentials
- fix awsCredentialsCheck
- replace nock with aws-sdk-client-mock
  • Loading branch information
hoonoh authored Jun 30, 2023
1 parent fb0c75c commit 0aa24e2
Show file tree
Hide file tree
Showing 31 changed files with 38,002 additions and 34,763 deletions.
1 change: 0 additions & 1 deletion .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ exclude_patterns:
- 'yarn.lock'
- sonar-project.properties
- sonar-project.properties
- webpack.config.js

checks:
file-lines:
Expand Down
1 change: 0 additions & 1 deletion .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ rules:
overrides:
- files:
- test/*.js
- webpack.config*.js
rules:
'@typescript-eslint/explicit-function-return-type': 0
'@typescript-eslint/no-var-requires': 0
Expand Down
1 change: 0 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,4 @@ src/
stats.json
test/
tsconfig*.json
webpack.config.js
yarn.lock
2 changes: 2 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
defaultSemverRangePrefix: ""

nodeLinker: node-modules

packageExtensions:
Expand Down
20 changes: 13 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
"build:ec2-types": "ts-node -T scripts/generate-ec2-types.ts",
"build:regions": "ts-node -T scripts/generate-regions.ts",
"build:types": "dts-bundle-generator -o dist/module.d.ts src/module.ts",
"build": "webpack && webpack -c ./webpack.config-cli.js",
"build:cli": "yarn esbuild --outdir=./dist --platform=node --format=cjs --bundle --minify --main-fields=module,main --sources-content=false ./src/cli.ts",
"build:module": "yarn esbuild --outdir=./dist --platform=node --format=cjs --bundle --minify --main-fields=module,main --sources-content=false ./src/module.ts",
"build": "yarn build:cli && yarn build:module",
"changelog": "conventional-changelog -i CHANGELOG.md -s -p angular",
"test": "jest --runInBand --verbose",
"test:coverage": "yarn test --coverage",
Expand All @@ -44,13 +46,16 @@
"semantic-release": "semantic-release"
},
"dependencies": {
"aws-sdk": "2.1404.0",
"ora": "5.4.1",
"prompts": "2.4.2",
"table": "6.8.1",
"yargs": "17.7.2"
},
"devDependencies": {
"@aws-sdk/client-ec2": "3.360.0",
"@aws-sdk/client-ssm": "3.360.0",
"@aws-sdk/client-sts": "3.360.0",
"@aws-sdk/smithy-client": "3.360.0",
"@commitlint/cli": "17.6.6",
"@semantic-release/changelog": "6.0.3",
"@semantic-release/commit-analyzer": "10.0.1",
Expand All @@ -60,16 +65,19 @@
"@semantic-release/release-notes-generator": "11.0.3",
"@types/jest": "29.5.2",
"@types/lodash": "4.14.195",
"@types/mock-fs": "4.13.1",
"@types/node": "18.16.18",
"@types/prettier": "2.7.3",
"@types/prompts": "2.4.4",
"@types/yargs": "17.0.24",
"@typescript-eslint/eslint-plugin": "5.60.0",
"@typescript-eslint/parser": "5.60.0",
"aws-sdk-client-mock": "2.2.0",
"commitizen": "4.3.0",
"conventional-changelog-cli": "3.0.0",
"cz-conventional-changelog": "3.3.0",
"dts-bundle-generator": "8.0.1",
"esbuild": "0.18.10",
"eslint": "8.43.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.0.0",
Expand All @@ -85,19 +93,17 @@
"jest-junit": "16.0.0",
"jest-mock-console": "2.0.0",
"lodash": "4.17.21",
"nock": "13.3.1",
"mock-fs": "5.2.0",
"prettier": "2.8.8",
"semantic-release": "21.0.5",
"ts-jest": "29.1.0",
"ts-loader": "9.4.3",
"ts-node": "10.9.1",
"typescript": "5.1.3",
"webpack": "5.88.0",
"webpack-cli": "5.1.4"
"typescript": "5.1.3"
},
"resolutions": {
"@babel/core": "7.22.5",
"minimist": "1.2.8",
"npm/chalk": "^4.1.2"
"npm/chalk": "4.1.2"
}
}
4 changes: 2 additions & 2 deletions scripts/generate-ec2-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { getEc2Info } from '../src/lib/core';
console.log(`found ${Object.keys(sorted).length} instance types`);

let output =
`import { InstanceType } from 'aws-sdk/clients/ec2';\n\n` +
`import { _InstanceType } from '@aws-sdk/client-ec2';\n\n` +
`export type Ec2InstanceInfo = { vCpu?: number; memoryGiB?: number };\n\n`;
output += `export const ec2Info: Record<InstanceType | string, Ec2InstanceInfo> = ${JSON.stringify(
output += `export const ec2Info: Record<_InstanceType | string, Ec2InstanceInfo> = ${JSON.stringify(
sorted,
)};`;
output = prettier.format(output, {
Expand Down
14 changes: 6 additions & 8 deletions scripts/generate-regions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import EC2 from 'aws-sdk/clients/ec2';
import SSM from 'aws-sdk/clients/ssm';
import { EC2 } from '@aws-sdk/client-ec2';
import { SSM } from '@aws-sdk/client-ssm';
import { readFileSync, writeFileSync } from 'fs';
import { diff } from 'jest-diff';
import { resolve } from 'path';
Expand All @@ -13,7 +13,7 @@ if (require.main && require.main.filename === module.filename) {
(async (): Promise<void> => {
const ec2 = new EC2({ region: 'us-east-1' });
const regions =
(await ec2.describeRegions({ AllRegions: true }).promise()).Regions?.sort(
(await ec2.describeRegions({ AllRegions: true })).Regions?.sort(
// order @ https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/
(a, b) => {
if (a.RegionName?.startsWith('us-') && !b.RegionName?.startsWith('us-')) return -1;
Expand Down Expand Up @@ -45,11 +45,9 @@ if (require.main && require.main.filename === module.filename) {
return {
region: r.RegionName,
longName: (
await ssm
.getParameter({
Name: `/aws/service/global-infrastructure/regions/${r.RegionName}/longName`,
})
.promise()
await ssm.getParameter({
Name: `/aws/service/global-infrastructure/regions/${r.RegionName}/longName`,
})
).Parameter?.Value,
};
}),
Expand Down
25 changes: 11 additions & 14 deletions scripts/generate-spot-prices-mock-data.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import EC2 from 'aws-sdk/clients/ec2';
import { EC2, SpotPrice } from '@aws-sdk/client-ec2';
import { readFileSync, writeFileSync } from 'fs';
import { find, uniqWith, xorWith } from 'lodash';
import { resolve } from 'path';
import yargs from 'yargs/yargs';

import { defaultRegions, Region } from '../src/constants/regions';

let allPrices: EC2.SpotPrice[] = [];
let allPrices: SpotPrice[] = [];

const fetchData = async (region: Region, token?: string): Promise<void> => {
process.stdout.write('.');
const ec2 = new EC2({ region });
const startTime = new Date();
startTime.setHours(startTime.getHours() - 3);
const results = await ec2
.describeSpotPriceHistory({ NextToken: token, StartTime: startTime })
.promise();
const results = await ec2.describeSpotPriceHistory({ NextToken: token, StartTime: startTime });
if (results.SpotPriceHistory) allPrices = [...allPrices, ...results.SpotPriceHistory];
if (results.NextToken) await fetchData(region, results.NextToken);
};
Expand Down Expand Up @@ -60,7 +58,7 @@ const { argv } = yargs()
// check for any duplicates
const unique = uniqWith(
allPrices,
(val1: EC2.SpotPrice, val2: EC2.SpotPrice) =>
(val1: SpotPrice, val2: SpotPrice) =>
val1.AvailabilityZone === val2.AvailabilityZone &&
val1.InstanceType === val2.InstanceType &&
val1.ProductDescription === val2.ProductDescription,
Expand All @@ -70,21 +68,20 @@ const { argv } = yargs()
if (args.processDetail) {
const uniqueProductDescription = uniqWith(
allPrices,
(val1: EC2.SpotPrice, val2: EC2.SpotPrice) =>
val1.ProductDescription === val2.ProductDescription,
(val1: SpotPrice, val2: SpotPrice) => val1.ProductDescription === val2.ProductDescription,
);
const uniqueType = uniqWith(
allPrices,
(val1: EC2.SpotPrice, val2: EC2.SpotPrice) => val1.InstanceType === val2.InstanceType,
(val1: SpotPrice, val2: SpotPrice) => val1.InstanceType === val2.InstanceType,
);
const uniqueFamily = uniqWith(
allPrices,
(val1: EC2.SpotPrice, val2: EC2.SpotPrice) =>
(val1: SpotPrice, val2: SpotPrice) =>
val1.InstanceType?.split('.').shift() === val2.InstanceType?.split('.').shift(),
);
const uniqueSize = uniqWith(
allPrices,
(val1: EC2.SpotPrice, val2: EC2.SpotPrice) =>
(val1: SpotPrice, val2: SpotPrice) =>
val1.InstanceType?.split('.').pop() === val2.InstanceType?.split('.').pop(),
);
console.log('uniqueType total:', uniqueType.length);
Expand All @@ -98,14 +95,14 @@ const { argv } = yargs()
const xor = xorWith(
unique,
prevList,
(val1: EC2.SpotPrice, val2: EC2.SpotPrice) =>
(val1: SpotPrice, val2: SpotPrice) =>
val1.AvailabilityZone === val2.AvailabilityZone &&
val1.InstanceType === val2.InstanceType &&
val1.ProductDescription === val2.ProductDescription,
);
console.log('xor total:', xor.length);
const xorPrev: EC2.SpotPrice[] = [];
const xorCur: EC2.SpotPrice[] = [];
const xorPrev: SpotPrice[] = [];
const xorCur: SpotPrice[] = [];
xor.forEach(p => {
const isCur = find(unique, p);
if (isCur !== undefined) xorCur.push(p);
Expand Down
4 changes: 2 additions & 2 deletions src/constants/ec2-info.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { InstanceType } from 'aws-sdk/clients/ec2';
import { _InstanceType } from '@aws-sdk/client-ec2';

export type Ec2InstanceInfo = { vCpu?: number; memoryGiB?: number };

export const ec2Info: Record<InstanceType | string, Ec2InstanceInfo> = {
export const ec2Info: Record<_InstanceType | string, Ec2InstanceInfo> = {
'a1.2xlarge': { vCpu: 8, memoryGiB: 16 },
'a1.4xlarge': { vCpu: 16, memoryGiB: 32 },
'a1.large': { vCpu: 2, memoryGiB: 4 },
Expand Down
48 changes: 24 additions & 24 deletions src/lib/core.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EC2Client, EC2ServiceException } from '@aws-sdk/client-ec2';
import { mockClient } from 'aws-sdk-client-mock';
import mockConsole, { RestoreConsole } from 'jest-mock-console';
import { filter } from 'lodash';
import nock from 'nock';

import { mockAwsCredentials, mockAwsCredentialsClear } from '../../test/mock-credential-endpoints';
import {
Expand All @@ -12,7 +13,7 @@ import { ec2Info, Ec2InstanceInfo } from '../constants/ec2-info';
import { InstanceFamilyType, InstanceSize } from '../constants/ec2-types';
import { Platform } from '../constants/platform';
import { Region } from '../constants/regions';
import { getEc2Info, getGlobalSpotPrices, isAWSError, SpotPriceExtended } from './core';
import { Ec2SpotPriceError, getEc2Info, getGlobalSpotPrices, SpotPriceExtended } from './core';

describe('lib', () => {
describe('getGlobalSpotPrices', () => {
Expand Down Expand Up @@ -208,16 +209,18 @@ describe('lib', () => {
describe('should handle error', () => {
const region: Region = 'ap-east-1';
let restoreConsole: RestoreConsole;
let ec2Mock: ReturnType<typeof mockClient> | undefined;

beforeAll(() => {
restoreConsole = mockConsole();
mockAwsCredentials();
nock(`https://ec2.${region}.amazonaws.com`).persist().post('/').reply(400, '');
ec2Mock = mockClient(EC2Client);
ec2Mock.rejectsOnce();
});
afterAll(() => {
restoreConsole();
mockAwsCredentialsClear();
nock.cleanAll();
ec2Mock?.restore();
});
it('should console log error', async () => {
await getGlobalSpotPrices({ regions: [region], reduceAZ: false });
Expand All @@ -228,35 +231,32 @@ describe('lib', () => {

describe('should handle auth error', () => {
const region: Region = 'ap-east-1';
let ec2Mock: ReturnType<typeof mockClient> | undefined;

beforeAll(() => {
mockAwsCredentials();
nock(`https://ec2.${region}.amazonaws.com`)
.persist()
.post('/')
.reply(
401,
`<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Errors>
<Error>
<Code>AuthFailure</Code>
<Message>AWS was not able to validate the provided access credentials</Message>
</Error>
</Errors>
<RequestID>e359d062-474b-4621-888c-e269b594de4a</RequestID>
</Response>`,
);
ec2Mock = mockClient(EC2Client);
ec2Mock.rejectsOnce(
new EC2ServiceException({
$fault: 'server',
$metadata: {
requestId: 'e359d062-474b-4621-888c-e269b594de4a',
},
name: 'AuthFailure',
message: 'AWS was not able to validate the provided access credentials',
}),
);
});
afterAll(() => {
mockAwsCredentialsClear();
nock.cleanAll();
ec2Mock?.restore();
});
it('should console log error', async () => {
try {
await getGlobalSpotPrices({ regions: [region], reduceAZ: false });
expect(true).toBeFalsy();
} catch (error) {
if (!isAWSError(error)) throw new Error('expected AWSError');
if (!Ec2SpotPriceError.isEc2SpotPriceError(error)) throw new Error('expected AWSError');
expect(error.name).toEqual('Ec2SpotPriceError');
expect(error.region).toEqual(region);
expect(error.code).toEqual('AuthFailure');
Expand All @@ -271,7 +271,7 @@ describe('lib', () => {

beforeAll(async () => {
restoreConsole = mockConsole();
mockDefaultRegionEndpoints({ returnRequestLimitExceededErrorCount: 10 });
mockDefaultRegionEndpoints({ returnRequestLimitExceededErrorCount: 5 });
results = await getGlobalSpotPrices({ regions: [region] });
});

Expand Down Expand Up @@ -377,7 +377,7 @@ describe('lib', () => {

beforeAll(async () => {
mockDefaultRegionEndpoints({
returnRequestLimitExceededErrorCount: 10,
returnRequestLimitExceededErrorCount: 5,
maxLength: 5,
returnPartialBlankValues: true,
});
Expand Down
Loading

0 comments on commit 0aa24e2

Please sign in to comment.