From 6e2e2cb6404e7165e79d06d301c59552d1bf92b0 Mon Sep 17 00:00:00 2001 From: hoonoh Date: Tue, 22 Oct 2019 19:52:55 +0900 Subject: [PATCH] feat: allow using familyType or size option by itself --- README.md | 9 ++--- src/cli.spec.ts | 12 ------ src/cli.ts | 6 --- src/lib.spec.ts | 100 +++++++++++++++++++++++++++++++++++++++++++++--- src/lib.ts | 36 +++++++++++++---- 5 files changed, 126 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index e9a5674f..021b8e1d 100644 --- a/README.md +++ b/README.md @@ -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` -#### --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 diff --git a/src/cli.spec.ts b/src/cli.spec.ts index f6a090cc..935af278 100644 --- a/src/cli.spec.ts +++ b/src/cli.spec.ts @@ -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 { diff --git a/src/cli.ts b/src/cli.ts index 39026a62..46bfe64c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -124,12 +124,6 @@ export const main = (argvInput?: string[]): Promise => 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(); if (familyType) { (familyType as InstanceFamilyType[]).forEach(t => { diff --git a/src/lib.spec.ts b/src/lib.spec.ts index 6be17e06..064cf10a 100644 --- a/src/lib.spec.ts +++ b/src/lib.spec.ts @@ -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', () => { @@ -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, }); @@ -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(); + } + }); + } }); }); diff --git a/src/lib.ts b/src/lib.ts index f6cc9470..f78f0ea2 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -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'; @@ -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(); + /* 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); } }