@moonset/util
Version:
165 lines (138 loc) • 4.48 kB
text/typescript
// A factory pattern to fetch SDK module for different accounts e.g. working
// accont and references account.
//
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/index.html
import * as AWS from 'aws-sdk';
// eslint-disable-next-line
import {ConfigurationOptions} from 'aws-sdk/lib/config';
import {CredentialPlugins} from 'aws-cdk/lib/api/aws-auth/credential-plugins';
import {Mode} from 'aws-cdk';
import {logger} from './log';
import {Config, ConfigConstant as CC, CONFIG_PATH} from './config';
export interface ISDK {
readonly currentRegion: string;
currentAccount(): Promise<Account>;
getSession(): Promise<string>;
s3(): AWS.S3;
ec2(): AWS.EC2;
emr(): AWS.EMR;
iam(): AWS.IAM;
stepfunctions(): AWS.StepFunctions;
sts(): AWS.STS;
cfn(): AWS.CloudFormation;
tagApi(): AWS.ResourceGroupsTaggingAPI;
}
export class SDK implements ISDK {
public readonly currentRegion: string;
private readonly config: ConfigurationOptions;
/**
* Default retry options for SDK clients.
*/
private readonly retryOptions = {
maxRetries: 6,
retryDelayOptions: {base: 300},
};
constructor(private readonly credentials: AWS.Credentials, region: string) {
this.config = {
...this.retryOptions,
credentials,
region,
};
this.currentRegion = region;
}
public s3(): AWS.S3 {
return new AWS.S3(this.config);
}
public ec2(): AWS.EC2 {
return new AWS.EC2(this.config);
}
public emr(): AWS.EMR {
return new AWS.EMR(this.config);
}
public iam(): AWS.IAM {
return new AWS.IAM(this.config);
}
public stepfunctions(): AWS.StepFunctions {
return new AWS.StepFunctions(this.config);
}
public cfn(): AWS.CloudFormation {
return new AWS.CloudFormation(this.config);
}
public sts(): AWS.STS {
return new AWS.STS(this.config);
}
public tagApi(): AWS.ResourceGroupsTaggingAPI {
return new AWS.ResourceGroupsTaggingAPI(this.config);
}
public async currentAccount(): Promise<Account> {
logger.info('Looking up default account ID from STS');
const result = await new AWS.STS(this.config)
.getCallerIdentity().promise();
const accountId = result.Account;
const partition = result.Arn!.split(':')[1];
if (!accountId) {
throw new Error('STS didn\'t return an account ID');
}
logger.info('Default account ID:', accountId);
return {accountId, partition};
}
// It might be a user or a role.
public async getSession() {
const sts = this.sts();
const currentUser = await sts.getCallerIdentity().promise();
const session = currentUser.Arn!
.split('/')
.slice(-1)[0]
.replace(/[^A-Za-z0-9-]/g, '-');
logger.info(`Current user is ${JSON.stringify(currentUser)},` +
` the extract session id is ${session}.`);
return session;
}
}
export class SDKProvider {
public static async forWorkingAccount(): Promise<ISDK> {
if (!process.env[CC.WORKING_ACCOUNT]) {
throw new Error('The working account is not specified.');
}
let credentials: AWS.Credentials | undefined;
const region = process.env[CC.WORKING_REGION];
if (Config.get(CC.WORKING_ACCESS_KEY) &&
Config.get(CC.WORKING_SECRET_KEY)) {
// Check config first.
logger.info(`Fetch Working account credentials from ${CONFIG_PATH}`);
credentials = new AWS.Credentials(
Config.get(CC.WORKING_ACCESS_KEY)!,
Config.get(CC.WORKING_SECRET_KEY)!,
);
} else {
// Load from CDK Credentials plugins.
credentials = await new CredentialPlugins().fetchCredentialsFor(
process.env[CC.WORKING_ACCOUNT]!,
Mode.ForWriting,
);
}
if (!credentials) {
throw new Error(`No credentials for account ${CC.WORKING_ACCOUNT}`);
}
if (!region) {
throw new Error(`The working account region is not specified.`);
}
return new SDK(credentials, region);
}
}
/**
* An AWS account
*
* An AWS account always exists in only one partition. Usually we don't care
* about the partition, but when we need to form ARNs we do.
*/
export interface Account {
/**
* The account number
*/
readonly accountId: string;
/**
* The partition ('aws' or 'aws-cn' or otherwise)
*/
readonly partition: string;
}