Skip to content

Commit

Permalink
feat: allow using familyType or size option by itself
Browse files Browse the repository at this point in the history
  • Loading branch information
hoonoh committed Oct 22, 2019
1 parent 6e551ec commit bd0af8b
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 37 deletions.
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,13 @@ Choose from: `general`, `compute`, `memory`, `storage`, `acceleratedComputing`
Type of EC2 instance to filter. Accepts multiple string values.
Enter valid EC2 instance type name. e.g. `-i t3.nano t3a.nano`

#### <a name="familyType"></a>--familyType | -f
#### --familyType | -f

EC2 Family type (`c4`, `c5`, etc..). Accepts multiple string values. Requires `--size` option to be used together.
Internally, `--familyType` and `--size` option will build list of EC2 instance types.
For example, `-f c4 c5 -s large xlarge` is equivalent to `-i c4.large c5.large c4.xlarge c5.xlarge`.
EC2 Family type (`c4`, `c5`, etc..). Accepts multiple string values.

#### --size | -s

EC2 size (`large`, `xlarge`, etc..). Accepts multiple string values. Requires `--familyType` option to be used together.
See [`--familyType`](#familyType) section for more detail.
EC2 size (`large`, `xlarge`, etc..). Accepts multiple string values.

#### --priceMax | -p

Expand Down
12 changes: 0 additions & 12 deletions src/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,6 @@ describe('cli', () => {
expect(consoleMockCallJoin()).toMatchSnapshot();
});

it('should handle invalid usage of instance family types and sizes option', async () => {
let caughtError = false;

try {
await main(['-f', 'c5']);
} catch (error) {
caughtError = true;
}
expect(caughtError).toBeTruthy();
expect(consoleMockCallJoin()).toMatchSnapshot();
});

it('should handle missing accessKeyId', async () => {
let caughtError = false;
try {
Expand Down
6 changes: 0 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,6 @@ export const main = (argvInput?: string[]): Promise<void> =>
secretAccessKey,
} = args.ui ? { ...(await ui()), instanceType: undefined } : args;

if ((!familyType && size) || (familyType && !size)) {
console.log('`familyTypes` or `sizes` attribute missing.');
rej();
return;
}

const familyTypeSet = new Set<InstanceFamilyType>();
if (familyType) {
(familyType as InstanceFamilyType[]).forEach(t => {
Expand Down
100 changes: 95 additions & 5 deletions src/lib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
mockDefaultRegionEndpointsClear,
} from '../test/mock-ec2-endpoints';
import { consoleMockCallJoin } from '../test/utils';
import { InstanceFamilyType, InstanceSize } from './ec2-types';
import { getGlobalSpotPrices } from './lib';
import { ProductDescription } from './product-description';
import { Region } from './regions';

describe('lib', () => {
Expand All @@ -36,15 +38,17 @@ describe('lib', () => {

describe('run with specific options', () => {
let results: SpotPrice[];
const familyTypes: InstanceFamilyType[] = ['c4', 'c5'];
const sizes: InstanceSize[] = ['large', 'xlarge'];
const productDescriptions: ProductDescription[] = ['Linux/UNIX'];

beforeAll(async () => {
mockDefaultRegionEndpoints({ maxLength: 5, returnPartialBlankValues: true });

results = await getGlobalSpotPrices({
familyTypes: ['c4', 'c5'],
sizes: ['large', 'xlarge'],
priceMax: 1,
productDescriptions: ['Linux/UNIX'],
familyTypes,
sizes,
productDescriptions,
limit: 20,
silent: true,
});
Expand All @@ -55,7 +59,93 @@ describe('lib', () => {
});

it('should return expected values', () => {
expect(results).toMatchSnapshot();
expect(results).toBeDefined();
expect(results.length).toEqual(20);
if (results) {
results.forEach(result => {
expect(result.InstanceType).toBeDefined();
expect(result.ProductDescription).toBeDefined();
if (result.InstanceType && result.ProductDescription) {
expect(
familyTypes.includes(result.InstanceType.split('.').shift() as InstanceFamilyType),
).toBeTruthy();
expect(
sizes.includes(result.InstanceType.split('.').pop() as InstanceSize),
).toBeTruthy();
expect(
productDescriptions.includes(result.ProductDescription as ProductDescription),
).toBeTruthy();
}
});
}
});
});

describe('run with family type only', () => {
let results: SpotPrice[];
const familyTypes: InstanceFamilyType[] = ['c1', 'c3', 'c4'];

beforeAll(async () => {
mockDefaultRegionEndpoints({ maxLength: 5, returnPartialBlankValues: true });

results = await getGlobalSpotPrices({
familyTypes,
limit: 20,
silent: true,
});
});

afterAll(() => {
mockDefaultRegionEndpointsClear();
});

it('should return expected values', () => {
expect(results).toBeDefined();
expect(results.length).toEqual(20);
if (results) {
results.forEach(result => {
expect(result.InstanceType).toBeDefined();
if (result.InstanceType) {
expect(
familyTypes.includes(result.InstanceType.split('.').shift() as InstanceFamilyType),
).toBeTruthy();
}
});
}
});
});

describe('run with family sizes only', () => {
let results: SpotPrice[];
const sizes: InstanceSize[] = ['small', 'medium', 'large'];

beforeAll(async () => {
mockDefaultRegionEndpoints({ maxLength: 5, returnPartialBlankValues: true });

results = await getGlobalSpotPrices({
sizes,
limit: 20,
silent: true,
});
});

afterAll(() => {
mockDefaultRegionEndpointsClear();
});

it('should return expected values', () => {
expect(results).toBeDefined();
expect(results.length).toEqual(20);
if (results) {
results.forEach(result => {
expect(result.InstanceType).toBeDefined();
if (result.InstanceType) {
expect(
sizes.includes(result.InstanceType.split('.').pop() as InstanceSize),
).toBeTruthy();
}
});
}
});
});

Expand Down
36 changes: 28 additions & 8 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { find, findIndex } from 'lodash';
import * as ora from 'ora';
import { table } from 'table';

import { InstanceFamilyType, InstanceSize, InstanceType } from './ec2-types';
import { allInstances, InstanceFamilyType, InstanceSize, InstanceType } from './ec2-types';
import { ProductDescription } from './product-description';
import { defaultRegions, Region, regionNames } from './regions';

Expand Down Expand Up @@ -155,17 +155,37 @@ export const getGlobalSpotPrices = async (options?: {

if (regions === undefined) regions = defaultRegions;

if (familyTypes && sizes) {
const instanceTypesGenerated: InstanceType[] = [];
familyTypes.forEach(family => {
if (familyTypes || sizes) {
const instanceTypesGenerated = new Set<InstanceType>();
/* istanbul ignore else */
if (familyTypes && sizes) {
familyTypes.forEach(type => {
sizes.forEach(size => {
instanceTypesGenerated.add(`${type}.${size}` as InstanceType);
});
});
} else if (familyTypes) {
familyTypes.forEach(type => {
allInstances
.filter((instance: InstanceType) => instance.startsWith(`${type}.`))
.forEach((instance: InstanceType) => {
instanceTypesGenerated.add(instance);
});
});
} else if (sizes) {
sizes.forEach(size => {
instanceTypesGenerated.push(`${family}.${size}` as InstanceType);
allInstances
.filter((instance: InstanceType) => instance.endsWith(`.${size}`))
.forEach((instance: InstanceType) => {
instanceTypesGenerated.add(instance);
});
});
});
}
const instanceTypesGeneratedArray = Array.from(instanceTypesGenerated);
if (!instanceTypes) {
instanceTypes = instanceTypesGenerated;
instanceTypes = instanceTypesGeneratedArray;
} else {
instanceTypes = instanceTypes.concat(instanceTypesGenerated);
instanceTypes = instanceTypes.concat(instanceTypesGeneratedArray);
}
}

Expand Down

0 comments on commit bd0af8b

Please sign in to comment.