From 2d5cee2af7bf536dfd9faddcbbb54de67eb4266d Mon Sep 17 00:00:00 2001 From: Akira Hayashi Date: Fri, 1 Mar 2024 19:20:57 +0900 Subject: [PATCH 1/4] Accept raw account ID to support account that has no account alias reason why need this: - want to support accounts that have not yet created aliases - This is because aliases [are public information](https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html) and there may be some decision-making costs involved in determining them - I want to make this tool available to organisations that have not gone through this. Alias of other account is unavailable because `listAccountAliases` does not accept argument `account_id` --- .prettierignore | 1 + package.json | 3 ++ pnpm-lock.yaml | 121 ++++++++++++++++++++++++++++++++++++++++++++ readme.md | 8 +++ src/account.test.ts | 112 ++++++++++++++++++++++++++++++++++++++++ src/account.ts | 22 +++++++- src/config.ts | 16 +++++- src/cost.ts | 54 ++++++++++++++------ src/index.ts | 3 ++ tsconfig.json | 1 + 10 files changed, 323 insertions(+), 18 deletions(-) create mode 100644 src/account.test.ts diff --git a/.prettierignore b/.prettierignore index 8d7d982..6072244 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ pnpm-lock.yaml CHANGELOG.md eslint.config.js +tsconfig.json diff --git a/package.json b/package.json index 43168e1..1becb9d 100644 --- a/package.json +++ b/package.json @@ -63,11 +63,13 @@ "ora": "^6.1.2" }, "devDependencies": { + "@aws-sdk/client-organizations": "^3.588.0", "@babel/core": "^7.24.6", "@babel/preset-env": "^7.24.6", "@babel/preset-typescript": "^7.24.6", "@eslint/js": "^9.4.0", "@smithy/types": "^3.0.0", + "@types/aws-sdk": "^2.7.0", "@types/eslint__js": "^8.42.3", "@types/jest": "^29.5.12", "@types/node": "^18.11.18", @@ -75,6 +77,7 @@ "@typescript-eslint/parser": "^7.11.0", "aws-sdk-client-mock": "^2.2.0", "aws-sdk-client-mock-jest": "^4.0.0", + "aws-sdk-mock": "^6.0.1", "babel-jest": "^29.7.0", "eslint": "^9.4.0", "jest": "^29.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf154ac..92d57f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,6 +36,9 @@ importers: specifier: ^6.1.2 version: 6.3.1 devDependencies: + '@aws-sdk/client-organizations': + specifier: ^3.588.0 + version: 3.588.0 '@babel/core': specifier: ^7.24.6 version: 7.24.6 @@ -51,6 +54,9 @@ importers: '@smithy/types': specifier: ^3.0.0 version: 3.0.0 + '@types/aws-sdk': + specifier: ^2.7.0 + version: 2.7.0 '@types/eslint__js': specifier: ^8.42.3 version: 8.42.3 @@ -72,6 +78,9 @@ importers: aws-sdk-client-mock-jest: specifier: ^4.0.0 version: 4.0.0(aws-sdk-client-mock@2.2.0) + aws-sdk-mock: + specifier: ^6.0.1 + version: 6.0.1 babel-jest: specifier: ^29.7.0 version: 29.7.0(@babel/core@7.24.6) @@ -127,6 +136,10 @@ packages: '@aws-crypto/util@3.0.0': resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + '@aws-sdk/client-organizations@3.588.0': + resolution: {integrity: sha512-4SduwkcATsLXaIMHKLzR2o8tGmxxiVu8Fc0LdAB/2bK0WqIqsAWE9OzzijxzxGnHI0aGQwLl99QIpP3izMss0g==} + engines: {node: '>=16.0.0'} + '@aws-sdk/client-sso-oidc@3.588.0': resolution: {integrity: sha512-CTbgtLSg0y2jIOtESuQKkRIqRe/FQmKuyzFWc+Qy6yGcbk1Pyusfz2BC+GGwpYU+1BlBBSNnLQHpx3XY87+aSA==} engines: {node: '>=16.0.0'} @@ -1150,6 +1163,9 @@ packages: '@sinonjs/samsam@7.0.1': resolution: {integrity: sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==} + '@sinonjs/samsam@8.0.0': + resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==} + '@sinonjs/text-encoding@0.7.2': resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==} @@ -1325,6 +1341,10 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@types/aws-sdk@2.7.0': + resolution: {integrity: sha512-bF6brnwPN9+kheqdKCpinMgCkj+sJIUEj+0v0LPug9OQwL5/1jy+kiJwl+Nkw4Kh+7oaL1phhC4gMz6Oq60jMg==} + deprecated: This is a stub types definition for aws-sdk (https://github.com/aws/aws-sdk-js). aws-sdk provides its own type definitions, so you don't need @types/aws-sdk installed! + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1525,6 +1545,10 @@ packages: aws-sdk-client-mock@2.2.0: resolution: {integrity: sha512-Kq2N+6gHRDedbrgTA0NMMfyN1XDWEA5Kbpm9/M/cenSxoNjfvQBOtBawI1lQe5h4UziLl///E7u17K9PBoHEKA==} + aws-sdk-mock@6.0.1: + resolution: {integrity: sha512-QpOGjTixFsl2EtCR7DBNCjgllYyJGO0FRSRTWISfyi08ItddxOkked/cBMdHEFMR3SIpK6pQTe7pe6tyoVh23A==} + engines: {node: '>=18.0.0'} + aws-sdk@2.1632.0: resolution: {integrity: sha512-doNHUxto00r7r9qWb5RcIsQHUTHdGiPPyJoXxmsmQW6aOr69BqYeuFl8SicNS1YzP6s5sFFnDNKq9KB7jA2fyA==} engines: {node: '>= 10.0.0'} @@ -2954,6 +2978,9 @@ packages: resolution: {integrity: sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==} deprecated: 16.1.1 + sinon@17.0.1: + resolution: {integrity: sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3101,6 +3128,10 @@ packages: tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + traverse@0.6.9: + resolution: {integrity: sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==} + engines: {node: '>= 0.4'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -3202,6 +3233,10 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} + typedarray.prototype.slice@1.0.3: + resolution: {integrity: sha512-8WbVAQAUlENo1q3c3zZYuy5k9VzBQvp8AX9WOtbvyWlLM1v5JaSRmjubLjzHF4JFtptjH/5c/i95yaElvcjC0A==} + engines: {node: '>= 0.4'} + typescript-eslint@7.11.0: resolution: {integrity: sha512-ZKe3yHF/IS/kCUE4CGE3UgtK+Q7yRk1e9kwEI0rqm9XxMTd9P1eHe0LVVtrZ3oFuIQ2unJ9Xn0vTsLApzJ3aPw==} engines: {node: ^18.18.0 || >=20.0.0} @@ -3399,6 +3434,52 @@ snapshots: '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 + '@aws-sdk/client-organizations@3.588.0': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sso-oidc': 3.588.0 + '@aws-sdk/client-sts': 3.588.0(@aws-sdk/client-sso-oidc@3.588.0) + '@aws-sdk/core': 3.588.0 + '@aws-sdk/credential-provider-node': 3.588.0(@aws-sdk/client-sso-oidc@3.588.0)(@aws-sdk/client-sts@3.588.0(@aws-sdk/client-sso-oidc@3.588.0)) + '@aws-sdk/middleware-host-header': 3.577.0 + '@aws-sdk/middleware-logger': 3.577.0 + '@aws-sdk/middleware-recursion-detection': 3.577.0 + '@aws-sdk/middleware-user-agent': 3.587.0 + '@aws-sdk/region-config-resolver': 3.587.0 + '@aws-sdk/types': 3.577.0 + '@aws-sdk/util-endpoints': 3.587.0 + '@aws-sdk/util-user-agent-browser': 3.577.0 + '@aws-sdk/util-user-agent-node': 3.587.0 + '@smithy/config-resolver': 3.0.1 + '@smithy/core': 2.1.1 + '@smithy/fetch-http-handler': 3.0.1 + '@smithy/hash-node': 3.0.0 + '@smithy/invalid-dependency': 3.0.0 + '@smithy/middleware-content-length': 3.0.0 + '@smithy/middleware-endpoint': 3.0.1 + '@smithy/middleware-retry': 3.0.3 + '@smithy/middleware-serde': 3.0.0 + '@smithy/middleware-stack': 3.0.0 + '@smithy/node-config-provider': 3.1.0 + '@smithy/node-http-handler': 3.0.0 + '@smithy/protocol-http': 4.0.0 + '@smithy/smithy-client': 3.1.1 + '@smithy/types': 3.0.0 + '@smithy/url-parser': 3.0.0 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.3 + '@smithy/util-defaults-mode-node': 3.0.3 + '@smithy/util-endpoints': 2.0.1 + '@smithy/util-middleware': 3.0.0 + '@smithy/util-retry': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + '@aws-sdk/client-sso-oidc@3.588.0': dependencies: '@aws-crypto/sha256-browser': 3.0.0 @@ -4810,6 +4891,12 @@ snapshots: lodash.get: 4.4.2 type-detect: 4.0.8 + '@sinonjs/samsam@8.0.0': + dependencies: + '@sinonjs/commons': 2.0.0 + lodash.get: 4.4.2 + type-detect: 4.0.8 + '@sinonjs/text-encoding@0.7.2': {} '@smithy/abort-controller@3.0.0': @@ -5079,6 +5166,10 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@types/aws-sdk@2.7.0': + dependencies: + aws-sdk: 2.1632.0 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.24.6 @@ -5315,6 +5406,12 @@ snapshots: sinon: 14.0.2 tslib: 2.6.2 + aws-sdk-mock@6.0.1: + dependencies: + aws-sdk: 2.1632.0 + sinon: 17.0.1 + traverse: 0.6.9 + aws-sdk@2.1632.0: dependencies: buffer: 4.9.2 @@ -7012,6 +7109,15 @@ snapshots: nise: 5.1.9 supports-color: 7.2.0 + sinon@17.0.1: + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 11.2.2 + '@sinonjs/samsam': 8.0.0 + diff: 5.2.0 + nise: 5.1.9 + supports-color: 7.2.0 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -7168,6 +7274,12 @@ snapshots: dependencies: punycode: 2.3.1 + traverse@0.6.9: + dependencies: + gopd: 1.0.1 + typedarray.prototype.slice: 1.0.3 + which-typed-array: 1.1.15 + tree-kill@1.2.2: {} ts-api-utils@1.3.0(typescript@4.9.5): @@ -7279,6 +7391,15 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 + typedarray.prototype.slice@1.0.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + typed-array-buffer: 1.0.2 + typed-array-byte-offset: 1.0.2 + typescript-eslint@7.11.0(eslint@9.4.0)(typescript@4.9.5): dependencies: '@typescript-eslint/eslint-plugin': 7.11.0(@typescript-eslint/parser@7.11.0(eslint@9.4.0)(typescript@4.9.5))(eslint@9.4.0)(typescript@4.9.5) diff --git a/readme.md b/readme.md index afb03d3..f82abb8 100644 --- a/readme.md +++ b/readme.md @@ -34,9 +34,11 @@ $ aws-cost --help -k, --access-key [key] AWS access key -s, --secret-key [key] AWS secret key + -t, --session-token [key] AWS session token -r, --region [region] AWS region (default: us-east-1) -a, --role-arn [arn] AWS role ARN to assume + --target-account [id] Account ID to see cost -p, --profile [profile] AWS profile to use (default: "default") -j, --json Get the output as JSON @@ -70,6 +72,12 @@ If you need to assume a role, you can pass the `role-arn` option: aws-cost -a arn:aws:iam::123456789012:role/your-role-arn ``` +If you need to query cost info of another account under your organization, you can pass the `--target-account` option: + +```bash +aws-cost --target-account 123456789012 +``` + ## Detailed Breakdown > The default usage is to get the cost breakdown by service diff --git a/src/account.test.ts b/src/account.test.ts new file mode 100644 index 0000000..1b4b2c1 --- /dev/null +++ b/src/account.test.ts @@ -0,0 +1,112 @@ +import { + OrganizationsClient, + DescribeAccountCommand, +} from '@aws-sdk/client-organizations'; +import AWS from 'aws-sdk'; +import { getAccountAlias } from './account'; +import { AWSConfig } from './config'; +import { mockClient } from 'aws-sdk-client-mock'; +import AWSMock from 'aws-sdk-mock'; + +describe('getAccountAlias', () => { + const organizationsMock = mockClient(OrganizationsClient); + + beforeEach(() => { + AWSMock.setSDKInstance(AWS); + organizationsMock.reset(); + }); + + afterEach(() => { + AWSMock.restore(); + jest.clearAllMocks(); + }); + + it('should return account alias when targetAccount is provided', async () => { + const awsConfig: AWSConfig = { + credentials: { + accessKeyId: 'testAccessKeyId', + secretAccessKey: 'testSecretAccessKey', + sessionToken: 'testSessionToken', + }, + region: 'us-east-1', + targetAccount: '123456789012', + }; + + const describeAccountCommandOutput = { + Account: { + Id: '123456789012', + Name: 'TestAccount', + }, + }; + + organizationsMock + .on(DescribeAccountCommand) + .resolves(describeAccountCommandOutput); + + const organizationsClientSpy = jest.spyOn( + OrganizationsClient.prototype, + 'send', + ); + + const alias = await getAccountAlias(awsConfig); + + expect(alias).toBe('TestAccount'); + expect(organizationsClientSpy).toHaveBeenCalledWith( + expect.any(DescribeAccountCommand), + ); + expect(organizationsClientSpy.mock.calls[0][0].input).toEqual({ + AccountId: '123456789012', + }); + + const clientInstance = organizationsClientSpy.mock.instances[0]; + const credentials = await clientInstance.config.credentials(); + expect(credentials.accessKeyId).toEqual('testAccessKeyId'); + expect(credentials.secretAccessKey).toEqual('testSecretAccessKey'); + expect(credentials.sessionToken).toEqual('testSessionToken'); + }); + + it('should return targetAccount when account name is not available', async () => { + const awsConfig: AWSConfig = { + credentials: { + accessKeyId: 'testAccessKeyId', + secretAccessKey: 'testSecretAccessKey', + sessionToken: 'testSessionToken', + }, + region: 'us-east-1', + targetAccount: '123456789012', + }; + + organizationsMock.on(DescribeAccountCommand).resolves({ + Account: { + Id: '123456789012', + }, + }); + + const alias = await getAccountAlias(awsConfig); + expect(alias).toBe('123456789012'); + }); + + it('should return account alias when targetAccount is not provided', async () => { + const awsConfig: AWSConfig = { + credentials: { + accessKeyId: 'testAccessKeyId', + secretAccessKey: 'testSecretAccessKey', + sessionToken: 'testSessionToken', + }, + region: 'us-east-1', + targetAccount: '', + }; + + AWSMock.mock('IAM', 'listAccountAliases', async () => { + return { AccountAliases: ['test-alias'] }; + }); + + // confirm that getCallerIdentity() will not be called in this test + const stsSpy = jest.fn(); + AWSMock.mock('STS', 'getCallerIdentity', stsSpy); + + const alias = await getAccountAlias(awsConfig); + expect(alias).toBe('test-alias'); + expect(stsSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/src/account.ts b/src/account.ts index 03057f4..ecfaef8 100644 --- a/src/account.ts +++ b/src/account.ts @@ -1,3 +1,7 @@ +import { + OrganizationsClient, + DescribeAccountCommand, +} from '@aws-sdk/client-organizations'; import AWS from 'aws-sdk'; import { AWSConfig } from './config'; import { showSpinner } from './logger'; @@ -5,10 +9,26 @@ import { showSpinner } from './logger'; export async function getAccountAlias(awsConfig: AWSConfig): Promise { showSpinner('Getting account alias'); + if (awsConfig.targetAccount) { + const organizations = new OrganizationsClient({ + region: 'us-east-1', // Organizations API is only available in us-east-1 + credentials: { + accessKeyId: awsConfig.credentials.accessKeyId, + secretAccessKey: awsConfig.credentials.secretAccessKey, + sessionToken: awsConfig.credentials.sessionToken, + }, + }); + const command = new DescribeAccountCommand({ + AccountId: awsConfig.targetAccount, + }); + const accountInfo = await organizations.send(command); + return accountInfo.Account?.Name || awsConfig.targetAccount; + } + const iam = new AWS.IAM(awsConfig); const accountAliases = await iam.listAccountAliases().promise(); - const foundAlias = accountAliases?.['AccountAliases']?.[0]; + const foundAlias = accountAliases?.AccountAliases?.[0]; if (foundAlias) { return foundAlias; diff --git a/src/config.ts b/src/config.ts index d1bd6fa..0437fa1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -16,6 +16,7 @@ export type AWSConfig = { sessionToken: string; }; region: string; + targetAccount: string; }; export async function getAwsConfigFromOptionsOrFile(options: { @@ -25,9 +26,17 @@ export async function getAwsConfigFromOptionsOrFile(options: { sessionToken; region: string; roleArn?: string; + targetAccount?: string; }): Promise { - const { profile, accessKey, secretKey, sessionToken, region, roleArn } = - options; + const { + profile, + accessKey, + secretKey, + sessionToken, + region, + roleArn, + targetAccount, + } = options; if (accessKey || secretKey) { if (!accessKey || !secretKey) { @@ -45,12 +54,14 @@ export async function getAwsConfigFromOptionsOrFile(options: { sessionToken: sessionToken, }, region: region, + targetAccount: '', }; } return { credentials: await loadAwsCredentials(profile, region, roleArn), region: region, + targetAccount: targetAccount, }; } @@ -124,6 +135,7 @@ async function loadAwsCredentials( ${chalk.bold(`--secret-key`)} ${chalk.bold(`--region`)} ${chalk.bold(`--role-arn`)} + ${chalk.bold(`--target-account`)} `); } } diff --git a/src/cost.ts b/src/cost.ts index a45d2bd..a4914ee 100644 --- a/src/cost.ts +++ b/src/cost.ts @@ -18,6 +18,41 @@ export async function getRawCostByService( const endDate = dayjs().subtract(1, 'day'); const startDate = endDate.subtract(65, 'day'); + const groupByConfig = [ + { + Type: 'DIMENSION', + Key: 'SERVICE', + }, + ]; + + let filterConfig = { + Not: { + Dimensions: { + Key: 'RECORD_TYPE', + Values: ['Credit', 'Refund', 'Upfront', 'Support'], + }, + }, + }; + + if (awsConfig.targetAccount) { + groupByConfig.push({ + Type: 'DIMENSION', + Key: 'LINKED_ACCOUNT', + }); + + filterConfig = { + And: [ + { + Dimensions: { + Key: 'LINKED_ACCOUNT', + Values: [awsConfig.targetAccount], + }, + }, + filterConfig, + ], + }; + } + // Get the cost and usage data for the specified account const pricingData = await costExplorer .getCostAndUsage({ @@ -26,21 +61,9 @@ export async function getRawCostByService( End: endDate.format('YYYY-MM-DD'), }, Granularity: 'DAILY', - Filter: { - Not: { - Dimensions: { - Key: 'RECORD_TYPE', - Values: ['Credit', 'Refund', 'Upfront', 'Support'], - }, - }, - }, + Filter: filterConfig, Metrics: ['UnblendedCost'], - GroupBy: [ - { - Type: 'DIMENSION', - Key: 'SERVICE', - }, - ], + GroupBy: groupByConfig, }) .promise(); @@ -48,7 +71,8 @@ export async function getRawCostByService( for (const day of pricingData.ResultsByTime) { for (const group of day.Groups) { - const serviceName = group.Keys[0]; + const filterKeys = group.Keys; + const serviceName = filterKeys.find((key) => !/^\d{12}$/.test(key)); // AWS service name is non-12-digits string const cost = group.Metrics.UnblendedCost.Amount; const costDate = day.TimePeriod.End; diff --git a/src/index.ts b/src/index.ts index 79ecfc0..d02b977 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,7 @@ program .option('-t, --session-Token [key]', 'AWS session Token') .option('-r, --region [region]', 'AWS region', 'us-east-1') .option('--role-arn [arn]', 'ARN of IAM role') + .option('--target-account [id]', 'Account ID to see cost') // Output variants .option('-j, --json', 'Get the output as JSON') .option('-u, --summary', 'Get only the summary without service breakdown') @@ -47,6 +48,7 @@ type OptionsType = { sessionToken: string; region: string; roleArn: string; + targetAccount: string; // AWS profile to use profile: string; // Output variants @@ -74,6 +76,7 @@ const awsConfig = await getAwsConfigFromOptionsOrFile({ sessionToken: options.sessionToken, region: options.region, roleArn: options.roleArn, + targetAccount: options.targetAccount, }); const alias = await getAccountAlias(awsConfig); diff --git a/tsconfig.json b/tsconfig.json index 8a09912..de4c3ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "isolatedModules": true, "baseUrl": ".", "outDir": "dist", + "typeRoots": ["node_modules/@types"], "noEmit": true }, "exclude": ["node_modules", "dist"], From 78d75ed25a4a30502f4a9469ee530f62dfa7bc70 Mon Sep 17 00:00:00 2001 From: Akira Hayashi Date: Mon, 3 Jun 2024 23:51:43 +0900 Subject: [PATCH 2/4] Fix typo --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f82abb8..1228dde 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ For the simple usage, just run the command without any options. aws-cost ``` -The output will be a the totals with breakdown by service. Optionally, you can pass the following options to modify the output: +The output will be the totals with breakdown by service. Optionally, you can pass the following options to modify the output: ```bash $ aws-cost --help From 7a4949ff9ae590f945e3184bbaf0704c2096d517 Mon Sep 17 00:00:00 2001 From: Akira Hayashi Date: Wed, 5 Jun 2024 04:02:12 +0900 Subject: [PATCH 3/4] Map `-a` for `--role-arn` --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index d02b977..d19e1ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,7 @@ program .option('-s, --secret-key [key]', 'AWS secret key') .option('-t, --session-Token [key]', 'AWS session Token') .option('-r, --region [region]', 'AWS region', 'us-east-1') - .option('--role-arn [arn]', 'ARN of IAM role') + .option('-a, --role-arn [arn]', 'ARN of IAM role') .option('--target-account [id]', 'Account ID to see cost') // Output variants .option('-j, --json', 'Get the output as JSON') From 0eb8cb76888fef0ee10b106a2f818a076ad397a1 Mon Sep 17 00:00:00 2001 From: Akira Hayashi Date: Wed, 5 Jun 2024 04:02:33 +0900 Subject: [PATCH 4/4] Map `-T` for `--target-account` --- readme.md | 2 +- src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 1228dde..fae5467 100644 --- a/readme.md +++ b/readme.md @@ -38,7 +38,7 @@ $ aws-cost --help -r, --region [region] AWS region (default: us-east-1) -a, --role-arn [arn] AWS role ARN to assume - --target-account [id] Account ID to see cost + -T, --target-account [id] Account ID to see cost -p, --profile [profile] AWS profile to use (default: "default") -j, --json Get the output as JSON diff --git a/src/index.ts b/src/index.ts index d19e1ee..4032c74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,7 @@ program .option('-t, --session-Token [key]', 'AWS session Token') .option('-r, --region [region]', 'AWS region', 'us-east-1') .option('-a, --role-arn [arn]', 'ARN of IAM role') - .option('--target-account [id]', 'Account ID to see cost') + .option('-T, --target-account [id]', 'Account ID to see cost') // Output variants .option('-j, --json', 'Get the output as JSON') .option('-u, --summary', 'Get only the summary without service breakdown')