From 6164bfd7013e7988a5f65bfa648312e3f82a444c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D=F0=9F=92=BB=20Romain=20M?= =?UTF-8?q?arcadier-Muller?= Date: Fri, 10 Aug 2018 13:07:03 +0200 Subject: [PATCH 1/2] Gracefully handle absence fo the `~/.aws/credentials` file The JS SDK assumes the file exists when it is passed as an argument, so this change makes the CDK Toolkit check for file existence before passing down to the SDK layer. Additionally, the SDK "ini file" re-implementation failed to check for file existece before attempting to load file contents. Fixes #540 --- packages/aws-cdk/lib/api/util/sdk.ts | 5 +++-- packages/aws-cdk/lib/api/util/sdk_ini_file.ts | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/aws-cdk/lib/api/util/sdk.ts b/packages/aws-cdk/lib/api/util/sdk.ts index cfcde34f1cd14..0dba62ef5d1b5 100644 --- a/packages/aws-cdk/lib/api/util/sdk.ts +++ b/packages/aws-cdk/lib/api/util/sdk.ts @@ -1,5 +1,6 @@ import { Environment} from '@aws-cdk/cx-api'; import AWS = require('aws-sdk'); +import fs = require('fs-extra'); import os = require('os'); import path = require('path'); import { debug } from '../../logging'; @@ -157,7 +158,7 @@ function makeCLICompatibleCredentialProvider(profile: string | undefined) { return new AWS.CredentialProviderChain([ () => new AWS.EnvironmentCredentials('AWS'), () => new AWS.EnvironmentCredentials('AMAZON'), - () => new AWS.SharedIniFileCredentials({ profile, filename }), + ...(fs.pathExistsSync(filename) ? [() => new AWS.SharedIniFileCredentials({ profile, filename })] : []), () => { // Calling private API if ((AWS.ECSCredentials.prototype as any).isConfiguredForEcsCredentials()) { @@ -200,4 +201,4 @@ function getCLICompatibleDefaultRegion(profile: string | undefined): string | un } return region; -} \ No newline at end of file +} diff --git a/packages/aws-cdk/lib/api/util/sdk_ini_file.ts b/packages/aws-cdk/lib/api/util/sdk_ini_file.ts index fc21bac2b0e49..8a34984630b1d 100644 --- a/packages/aws-cdk/lib/api/util/sdk_ini_file.ts +++ b/packages/aws-cdk/lib/api/util/sdk_ini_file.ts @@ -6,6 +6,7 @@ */ import AWS = require('aws-sdk'); +import fs = require('fs-extra'); import os = require('os'); import path = require('path'); @@ -44,9 +45,9 @@ export class SharedIniFile { private ensureFileLoaded() { if (!this.parsedContents) { - this.parsedContents = (AWS as any).util.ini.parse( - (AWS as any).util.readFileSync(this.filename) - ); + this.parsedContents = fs.pathExistsSync(this.filename) + ? (AWS as any).util.ini.parse((AWS as any).util.readFileSync(this.filename)) + : {}; } } -} \ No newline at end of file +} From ea023f9479d8175e2ab84ebdff4f20299784bb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D=F0=9F=92=BB=20Romain=20M?= =?UTF-8?q?arcadier-Muller?= Date: Tue, 14 Aug 2018 09:57:33 +0200 Subject: [PATCH 2/2] Make async --- packages/aws-cdk/bin/cdk.ts | 2 +- packages/aws-cdk/lib/api/util/sdk.ts | 18 ++++++++++-------- packages/aws-cdk/lib/api/util/sdk_ini_file.ts | 10 +++++----- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index e73e37f8ff994..9aaaa73f05721 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -729,7 +729,7 @@ async function initCommandLine() { */ async function populateDefaultEnvironmentIfNeeded(context: any) { if (!(cxapi.DEFAULT_REGION_CONTEXT_KEY in context)) { - context[cxapi.DEFAULT_REGION_CONTEXT_KEY] = aws.defaultRegion(); + context[cxapi.DEFAULT_REGION_CONTEXT_KEY] = await aws.defaultRegion(); debug(`Setting "${cxapi.DEFAULT_REGION_CONTEXT_KEY}" context to`, context[cxapi.DEFAULT_REGION_CONTEXT_KEY]); } diff --git a/packages/aws-cdk/lib/api/util/sdk.ts b/packages/aws-cdk/lib/api/util/sdk.ts index 0dba62ef5d1b5..49e24cb84658a 100644 --- a/packages/aws-cdk/lib/api/util/sdk.ts +++ b/packages/aws-cdk/lib/api/util/sdk.ts @@ -23,13 +23,12 @@ export class SDK { private defaultAccountId?: string = undefined; private readonly userAgent: string; private readonly accountCache = new AccountAccessKeyCache(); - private readonly defaultCredentialProvider: AWS.CredentialProviderChain; + private defaultCredentialProvider?: AWS.CredentialProviderChain; constructor(private readonly profile: string | undefined) { // Find the package.json from the main toolkit const pkg = (require.main as any).require('../package.json'); this.userAgent = `${pkg.name}/${pkg.version}`; - this.defaultCredentialProvider = makeCLICompatibleCredentialProvider(profile); } public async cloudFormation(environment: Environment, mode: Mode): Promise { @@ -64,8 +63,8 @@ export class SDK { }); } - public defaultRegion(): string | undefined { - return getCLICompatibleDefaultRegion(this.profile); + public async defaultRegion(): Promise { + return await getCLICompatibleDefaultRegion(this.profile); } public async defaultAccount(): Promise { @@ -79,6 +78,9 @@ export class SDK { private async lookupDefaultAccount() { try { debug('Resolving default credentials'); + if (!this.defaultCredentialProvider) { + this.defaultCredentialProvider = await makeCLICompatibleCredentialProvider(this.profile); + } const creds = await this.defaultCredentialProvider.resolvePromise(); const accessKeyId = creds.accessKeyId; if (!accessKeyId) { @@ -148,7 +150,7 @@ export class SDK { * file location is not given (SDK expects explicit environment variable with name). * - AWS_DEFAULT_PROFILE is also inspected for profile name (not just AWS_PROFILE). */ -function makeCLICompatibleCredentialProvider(profile: string | undefined) { +async function makeCLICompatibleCredentialProvider(profile: string | undefined) { profile = profile || process.env.AWS_PROFILE || process.env.AWS_DEFAULT_PROFILE || 'default'; // Need to construct filename ourselves, without appropriate environment variables @@ -158,7 +160,7 @@ function makeCLICompatibleCredentialProvider(profile: string | undefined) { return new AWS.CredentialProviderChain([ () => new AWS.EnvironmentCredentials('AWS'), () => new AWS.EnvironmentCredentials('AMAZON'), - ...(fs.pathExistsSync(filename) ? [() => new AWS.SharedIniFileCredentials({ profile, filename })] : []), + ...(await fs.pathExists(filename) ? [() => new AWS.SharedIniFileCredentials({ profile, filename })] : []), () => { // Calling private API if ((AWS.ECSCredentials.prototype as any).isConfiguredForEcsCredentials()) { @@ -182,7 +184,7 @@ function makeCLICompatibleCredentialProvider(profile: string | undefined) { * - AWS_DEFAULT_PROFILE and AWS_DEFAULT_REGION are also used as environment * variables to be used to determine the region. */ -function getCLICompatibleDefaultRegion(profile: string | undefined): string | undefined { +async function getCLICompatibleDefaultRegion(profile: string | undefined): Promise { profile = profile || process.env.AWS_PROFILE || process.env.AWS_DEFAULT_PROFILE || 'default'; // Defaults inside constructor @@ -196,7 +198,7 @@ function getCLICompatibleDefaultRegion(profile: string | undefined): string | un while (!region && toCheck.length > 0) { const configFile = new SharedIniFile(toCheck.shift()); - const section = configFile.getProfile(profile); + const section = await configFile.getProfile(profile); region = section && section.region; } diff --git a/packages/aws-cdk/lib/api/util/sdk_ini_file.ts b/packages/aws-cdk/lib/api/util/sdk_ini_file.ts index 8a34984630b1d..826a5a5ffe992 100644 --- a/packages/aws-cdk/lib/api/util/sdk_ini_file.ts +++ b/packages/aws-cdk/lib/api/util/sdk_ini_file.ts @@ -26,8 +26,8 @@ export class SharedIniFile { this.filename = options.filename || this.getDefaultFilepath(); } - public getProfile(profile: string) { - this.ensureFileLoaded(); + public async getProfile(profile: string) { + await this.ensureFileLoaded(); const profileIndex = profile !== (AWS as any).util.defaultProfile && this.isConfig ? 'profile ' + profile : profile; @@ -43,10 +43,10 @@ export class SharedIniFile { ); } - private ensureFileLoaded() { + private async ensureFileLoaded() { if (!this.parsedContents) { - this.parsedContents = fs.pathExistsSync(this.filename) - ? (AWS as any).util.ini.parse((AWS as any).util.readFileSync(this.filename)) + this.parsedContents = await fs.pathExists(this.filename) + ? (AWS as any).util.ini.parse(await fs.readFile(this.filename)) : {}; } }