Skip to content

Commit

Permalink
feat: add instance family option
Browse files Browse the repository at this point in the history
also changed cli option names from plural to singular.
  • Loading branch information
hoonoh committed Oct 17, 2019
1 parent a299867 commit c6e211c
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 120 deletions.
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,31 @@ CLI utility to list current global AWS EC2 Spot Instance prices. Requires valid

## Options

### --regions | -r
### --region | -r

AWS regions to search. Accepts multiple string values.
Defaults to all available AWS regions which does not require opt-in.
AWS region to fetch data from. Accepts multiple string values.
Defaults to all available AWS region which does not require opt-in.

### --instanceTypes | -i
### --family

EC2 instance families to filter. Accepts multiple string values.
Choose from: `general`, `compute`, `memory`, `storage`, `acceleratedComputing`

### --instanceType | -i

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="families"></a>--families | -f
### <a name="familyType"></a>--familyType | -f

EC2 Family type (`c4`, `c5`, etc..). Accepts multiple string values. Requires `--sizes` option to be used together.
Internally, `--families` and `--sizes` option will build list of EC2 instance types.
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`.

### --sizes | -s
### --size | -s

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

### --limit | -l

Expand All @@ -37,7 +42,7 @@ Limits list of price information items to be returned.

Maximum price.

### --productDescriptions | -d
### --productDescription | -d

Instance product description to filter. Accepts multiple string values.
You can use `linux` or `windows` (all in lowercase) as wildcard.
Expand Down
100 changes: 52 additions & 48 deletions docs/preview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
88 changes: 60 additions & 28 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import * as yargs from 'yargs';

import {
allInstances,
instanceFamilies,
InstanceFamily,
instanceFamily,
InstanceFamilyType,
instanceFamilyTypes,
InstanceSize,
instanceSizes,
InstanceType,
Expand All @@ -24,28 +25,34 @@ export const main = (argvInput?: string[]) =>
'$0',
'get current AWS spot instance prices',
{
regions: {
region: {
alias: 'r',
describe: 'AWS regions.',
type: 'array',
choices: defaultRegions,
string: true,
},
instanceTypes: {
instanceType: {
alias: 'i',
describe: 'EC2 type',
type: 'array',
choices: allInstances,
string: true,
},
families: {
family: {
describe: 'EC2 instance family.',
type: 'array',
string: true,
choices: Object.keys(instanceFamily),
},
familyType: {
alias: 'f',
describe: 'EC2 instance families. Requires `sizes` parameter.',
describe: 'EC2 instance family types. Requires `sizes` parameter.',
type: 'array',
string: true,
choices: instanceFamilies,
choices: instanceFamilyTypes,
},
sizes: {
size: {
alias: 's',
describe: 'EC2 instance sizes. Requires `families` parameter.',
type: 'array',
Expand All @@ -70,7 +77,7 @@ export const main = (argvInput?: string[]) =>
describe: 'Maximum price',
type: 'number',
},
productDescriptions: {
productDescription: {
alias: 'd',
describe:
'Product descriptions. Choose `windows` or `linux` (all lowercase) as wildcard.',
Expand All @@ -95,31 +102,53 @@ export const main = (argvInput?: string[]) =>

async args => {
const {
regions,
instanceTypes,
families,
sizes,
region,
instanceType,
family,
familyType,
size,
limit,
priceMax,
productDescriptions,
productDescription,
accessKeyId,
secretAccessKey,
} = args;

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

// process instance types
let instanceTypeSet: Set<InstanceType> | undefined;
if (instanceType) {
instanceTypeSet = new Set();
(instanceType as InstanceType[]).forEach(type => {
instanceTypeSet!.add(type);
});
}

// process instance families
if (family) {
if (!instanceTypeSet) instanceTypeSet = new Set();
(family as (keyof typeof instanceFamily)[]).forEach(f => {
instanceFamily[f].forEach((type: InstanceFamilyType) => {
allInstances
.filter(instance => instance.startsWith(type))
.forEach(instance => instanceTypeSet!.add(instance));
});
});
}

// process product description
function instanceOfProductDescription(pd: string): pd is ProductDescription {
return allProductDescriptions.indexOf(pd as ProductDescription) >= 0;
return allProductDescriptions.includes(pd as ProductDescription);
}
let productDescriptionsSet: Set<ProductDescription> | undefined;
if (productDescriptions) {
if (productDescription) {
productDescriptionsSet = new Set<ProductDescription>();
(productDescriptions as (
(productDescription as (
| ProductDescription
| keyof typeof productDescriptionWildcards)[]).forEach(pd => {
if (instanceOfProductDescription(pd)) {
Expand Down Expand Up @@ -159,20 +188,23 @@ export const main = (argvInput?: string[]) =>
console.log('Querying current spot prices with options:');
console.group();
console.log('limit:', limit);
if (regions) console.log('regions:', regions);
if (instanceTypes) console.log('instanceTypes:', instanceTypes);
if (families) console.log('families:', families);
if (sizes) console.log('sizes:', sizes);
if (region) console.log('regions:', region.join(', '));
if (instanceTypeSet)
console.log('instanceTypes:', Array.from(instanceTypeSet).join(', '));
if (familyType) console.log('familyTypes:', familyType.join(', '));
if (size) console.log('sizes:', size.join(', '));
if (priceMax) console.log('priceMax:', priceMax);
if (productDescriptionsSet)
console.log('productDescriptions:', Array.from(productDescriptionsSet));
console.log('productDescriptions:', Array.from(productDescriptionsSet).join(', '));
console.groupEnd();

await getGlobalSpotPrices({
regions: regions as Region[],
instanceTypes: instanceTypes as InstanceType[],
families: families as InstanceFamily[],
sizes: sizes as InstanceSize[],
regions: region as Region[],
instanceTypes: instanceTypeSet
? (Array.from(instanceTypeSet) as InstanceType[])
: undefined,
familyTypes: familyType as InstanceFamilyType[],
sizes: size as InstanceSize[],
limit,
priceMax,
productDescriptions: productDescriptionsSet
Expand Down
41 changes: 28 additions & 13 deletions src/ec2-types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const instanceFamilies = [
export const instanceFamilyGeneral = [
'a1',
't1',
't2',
Expand All @@ -14,12 +14,11 @@ export const instanceFamilies = [
'm5d',
'm5dn',
'm5n',
'c1',
'c3',
'c4',
'c5',
'c5d',
'c5n',
] as const;

export const instanceFamilyCompute = ['c1', 'c3', 'c4', 'c5', 'c5d', 'c5n'] as const;

export const instanceFamilyMemory = [
'r3',
'r4',
'r5',
Expand All @@ -31,11 +30,11 @@ export const instanceFamilies = [
'x1',
'x1e',
'z1d',
'd2',
'h1',
'i2',
'i3',
'i3en',
] as const;

export const instanceFamilyStorage = ['d2', 'h1', 'i2', 'i3', 'i3en'] as const;

export const instanceFamilyAcceleratedComputing = [
'f1',
'g2',
'g3',
Expand All @@ -46,7 +45,23 @@ export const instanceFamilies = [
'p3dn',
] as const;

export type InstanceFamily = typeof instanceFamilies[number];
export const instanceFamily = {
general: instanceFamilyGeneral,
compute: instanceFamilyCompute,
memory: instanceFamilyMemory,
storage: instanceFamilyStorage,
acceleratedComputing: instanceFamilyAcceleratedComputing,
};

export const instanceFamilyTypes = [
...instanceFamilyGeneral,
...instanceFamilyCompute,
...instanceFamilyMemory,
...instanceFamilyStorage,
...instanceFamilyAcceleratedComputing,
];

export type InstanceFamilyType = typeof instanceFamilyTypes[number];

export const instanceSizes = [
'nano',
Expand Down
11 changes: 6 additions & 5 deletions src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { EC2, STS } from 'aws-sdk';
import { find, findIndex } from 'lodash';
import { table } from 'table';

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

Expand Down Expand Up @@ -96,7 +96,8 @@ export const defaults = {

export const getGlobalSpotPrices = async (options?: {
regions?: Region[];
families?: InstanceFamily[];
// families?:
familyTypes?: InstanceFamilyType[];
sizes?: InstanceSize[];
priceMax?: number;
instanceTypes?: InstanceType[];
Expand All @@ -107,7 +108,7 @@ export const getGlobalSpotPrices = async (options?: {
secretAccessKey?: string;
}) => {
const {
families,
familyTypes,
sizes,
priceMax,
productDescriptions,
Expand All @@ -123,9 +124,9 @@ export const getGlobalSpotPrices = async (options?: {

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

if (families && sizes) {
if (familyTypes && sizes) {
if (!instanceTypes) instanceTypes = [];
families.forEach(family => {
familyTypes.forEach(family => {
sizes.forEach(size => {
instanceTypes!.push(`${family}.${size}` as InstanceType);
});
Expand Down
7 changes: 2 additions & 5 deletions test/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,12 @@ export const nockEndpoint = (options: {

const instanceData: SpotPrice[] = filter(regionalData[region], (o: SpotPrice) => {
let rtn = true;
if (
instanceTypes.length &&
(!o.InstanceType || instanceTypes.indexOf(o.InstanceType) < 0)
) {
if (instanceTypes.length && (!o.InstanceType || instanceTypes.includes(o.InstanceType))) {
rtn = false;
}
if (
productDescriptions.length &&
(!o.ProductDescription || productDescriptions.indexOf(o.ProductDescription) < 0)
(!o.ProductDescription || !productDescriptions.includes(o.ProductDescription))
) {
rtn = false;
}
Expand Down
Loading

0 comments on commit c6e211c

Please sign in to comment.