diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts index c055e9947..b0dc5c51c 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts +++ b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts @@ -45,6 +45,12 @@ export const IO = { description: 'Default trace messages emitted from the Toolkit', }), + // warnings & errors + CDK_TOOLKIT_W0100: make.warn({ + code: 'CDK_TOOLKIT_W0100', + description: 'Credential plugin warnings', + }), + // 1: Synth (1xxx) CDK_TOOLKIT_I1000: make.info({ code: 'CDK_TOOLKIT_I1000', @@ -268,7 +274,6 @@ export const IO = { description: 'Hotswap disclosure message', }), - // errors CDK_TOOLKIT_E5001: make.error({ code: 'CDK_TOOLKIT_E5001', description: 'No stacks found', @@ -482,9 +487,17 @@ export const IO = { }), // SDK codes - CDK_SDK_I0000: make.trace({ + DEFAULT_SDK_TRACE: make.trace({ code: 'CDK_SDK_I0000', - description: 'An SDK message.', + description: 'An SDK trace message.', + }), + DEFAULT_SDK_DEBUG: make.debug({ + code: 'CDK_SDK_I0000', + description: 'An SDK debug message.', + }), + DEFAULT_SDK_WARN: make.warn({ + code: 'CDK_SDK_W0000', + description: 'An SDK warning message.', }), CDK_SDK_I0100: make.trace({ code: 'CDK_SDK_I0100', diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/testing/test-io-host.ts b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/testing/test-io-host.ts index 2bbbbccde..0ea426fdf 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/testing/test-io-host.ts +++ b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/testing/test-io-host.ts @@ -1,6 +1,8 @@ import { RequireApproval } from '../../../require-approval'; import type { IIoHost } from '../../io-host'; import type { IoMessage, IoMessageLevel, IoRequest } from '../../io-message'; +import type { IoHelper } from '../io-helper'; +import { asIoHelper } from '../io-helper'; import { isMessageRelevantForLevel } from '../level-priority'; /** @@ -26,6 +28,10 @@ export class TestIoHost implements IIoHost { this.requestSpy = jest.fn(); } + public asHelper(action = 'synth'): IoHelper { + return asIoHelper(this, action as any); + } + public async notify(msg: IoMessage): Promise { if (isMessageRelevantForLevel(msg, this.level)) { this.notifySpy(msg); diff --git a/packages/@aws-cdk/toolkit-lib/docs/message-registry.md b/packages/@aws-cdk/toolkit-lib/docs/message-registry.md index e16450181..b54f8272f 100644 --- a/packages/@aws-cdk/toolkit-lib/docs/message-registry.md +++ b/packages/@aws-cdk/toolkit-lib/docs/message-registry.md @@ -11,6 +11,7 @@ group: Documents | `CDK_TOOLKIT_W0000` | Default warning messages emitted from the Toolkit | `warn` | n/a | | `CDK_TOOLKIT_E0000` | Default error messages emitted from the Toolkit | `error` | n/a | | `CDK_TOOLKIT_I0000` | Default trace messages emitted from the Toolkit | `trace` | n/a | +| `CDK_TOOLKIT_W0100` | Credential plugin warnings | `warn` | n/a | | `CDK_TOOLKIT_I1000` | Provides synthesis times. | `info` | {@link Duration} | | `CDK_TOOLKIT_I1001` | Cloud Assembly synthesis is starting | `trace` | {@link StackSelectionDetails} | | `CDK_TOOLKIT_I1901` | Provides stack data | `result` | {@link StackAndAssemblyData} | @@ -95,5 +96,7 @@ group: Documents | `CDK_ASSEMBLY_I9999` | Annotations emitted by the cloud assembly | `info` | [cxapi.SynthesisMessage](https://docs.aws.amazon.com/cdk/api/v2/docs/@aws-cdk_cx-api.SynthesisMessage.html) | | `CDK_ASSEMBLY_W9999` | Warnings emitted by the cloud assembly | `warn` | [cxapi.SynthesisMessage](https://docs.aws.amazon.com/cdk/api/v2/docs/@aws-cdk_cx-api.SynthesisMessage.html) | | `CDK_ASSEMBLY_E9999` | Errors emitted by the cloud assembly | `error` | [cxapi.SynthesisMessage](https://docs.aws.amazon.com/cdk/api/v2/docs/@aws-cdk_cx-api.SynthesisMessage.html) | -| `CDK_SDK_I0000` | An SDK message. | `trace` | n/a | +| `CDK_SDK_I0000` | An SDK trace message. | `trace` | n/a | +| `CDK_SDK_I0000` | An SDK debug message. | `debug` | n/a | +| `CDK_SDK_W0000` | An SDK warning message. | `warn` | n/a | | `CDK_SDK_I0100` | An SDK trace. SDK traces are emitted as traces to the IoHost, but contain the original SDK logging level. | `trace` | {@link SdkTrace} | diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index 38abf69fb..b66c84710 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -128,9 +128,11 @@ export class Toolkit extends CloudAssemblySourceBuilder implements AsyncDisposab private async sdkProvider(action: ToolkitAction): Promise { // @todo this needs to be different instance per action if (!this._sdkProvider) { + const ioHelper = asIoHelper(this.ioHost, action); this._sdkProvider = await SdkProvider.withAwsCliCompatibleDefaults({ ...this.props.sdkConfig, - logger: asSdkLogger(asIoHelper(this.ioHost, action)), + ioHelper, + logger: asSdkLogger(ioHelper), }); } diff --git a/packages/aws-cdk/lib/api/aws-auth/account-cache.ts b/packages/aws-cdk/lib/api/aws-auth/account-cache.ts index ae58a57c0..56c37fd29 100644 --- a/packages/aws-cdk/lib/api/aws-auth/account-cache.ts +++ b/packages/aws-cdk/lib/api/aws-auth/account-cache.ts @@ -1,7 +1,6 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import type { Account } from './sdk-provider'; -import { debug } from '../../logging'; import { cdkCacheDir } from '../../util'; /** @@ -16,13 +15,24 @@ export class AccountAccessKeyCache { */ public static readonly MAX_ENTRIES = 1000; + /** + * The default path used for the accounts access key cache + */ + public static get DEFAULT_PATH(): string { + // needs to be a getter because cdkCacheDir can be set via env variable and might change + return path.join(cdkCacheDir(), 'accounts_partitions.json'); + } + private readonly cacheFile: string; + private readonly debug: (msg: string) => Promise; + /** * @param filePath Path to the cache file */ - constructor(filePath?: string) { - this.cacheFile = filePath || path.join(cdkCacheDir(), 'accounts_partitions.json'); + constructor(filePath: string = AccountAccessKeyCache.DEFAULT_PATH, debugFn: (msg: string) => Promise) { + this.cacheFile = filePath; + this.debug = debugFn; } /** @@ -40,7 +50,7 @@ export class AccountAccessKeyCache { // try to get account ID based on this access key ID from disk. const cached = await this.get(accessKeyId); if (cached) { - debug(`Retrieved account ID ${cached.accountId} from disk cache`); + await this.debug(`Retrieved account ID ${cached.accountId} from disk cache`); return cached; } diff --git a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts index fb5b6f1c3..5c12ce48e 100644 --- a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts +++ b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts @@ -1,15 +1,15 @@ +import { format } from 'node:util'; import { createCredentialChain, fromEnv, fromIni, fromNodeProviderChain } from '@aws-sdk/credential-providers'; import { MetadataService } from '@aws-sdk/ec2-metadata-service'; import type { NodeHttpHandlerOptions } from '@smithy/node-http-handler'; import { loadSharedConfigFiles } from '@smithy/shared-ini-file-loader'; import type { AwsCredentialIdentityProvider, Logger } from '@smithy/types'; import * as promptly from 'promptly'; -import { ProxyAgent } from 'proxy-agent'; import { makeCachingProvider } from './provider-caching'; +import { ProxyAgentProvider } from './proxy-agent'; import type { SdkHttpOptions } from './sdk-provider'; -import { readIfPossible } from './util'; import { AuthenticationError } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api'; -import { debug } from '../../logging'; +import { IO, type IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; const DEFAULT_CONNECTION_TIMEOUT = 10000; const DEFAULT_TIMEOUT = 300000; @@ -23,16 +23,22 @@ const DEFAULT_TIMEOUT = 300000; * https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html */ export class AwsCliCompatible { + private readonly ioHelper: IoHelper; + + public constructor(ioHelper: IoHelper) { + this.ioHelper = ioHelper; + } + /** * Build an AWS CLI-compatible credential chain provider * * The credential chain returned by this function is always caching. */ - public static async credentialChainBuilder( + public async credentialChainBuilder( options: CredentialChainOptions = {}, ): Promise { const clientConfig = { - requestHandler: AwsCliCompatible.requestHandlerBuilder(options.httpOptions), + requestHandler: await this.requestHandlerBuilder(options.httpOptions), customUserAgent: 'aws-cdk', logger: options.logger, }; @@ -63,7 +69,7 @@ export class AwsCliCompatible { return makeCachingProvider(fromIni({ profile: options.profile, ignoreCache: true, - mfaCodeProvider: tokenCodeFn, + mfaCodeProvider: this.tokenCodeFn.bind(this), clientConfig, parentClientConfig, logger: options.logger, @@ -100,7 +106,7 @@ export class AwsCliCompatible { clientConfig, parentClientConfig, logger: options.logger, - mfaCodeProvider: tokenCodeFn, + mfaCodeProvider: this.tokenCodeFn.bind(this), ignoreCache: true, }); @@ -109,8 +115,8 @@ export class AwsCliCompatible { : nodeProviderChain; } - public static requestHandlerBuilder(options: SdkHttpOptions = {}): NodeHttpHandlerOptions { - const agent = this.proxyAgent(options); + public async requestHandlerBuilder(options: SdkHttpOptions = {}): Promise { + const agent = await new ProxyAgentProvider(this.ioHelper).create(options); return { connectionTimeout: DEFAULT_CONNECTION_TIMEOUT, @@ -120,19 +126,6 @@ export class AwsCliCompatible { }; } - public static proxyAgent(options: SdkHttpOptions) { - // Force it to use the proxy provided through the command line. - // Otherwise, let the ProxyAgent auto-detect the proxy using environment variables. - const getProxyForUrl = options.proxyAddress != null - ? () => Promise.resolve(options.proxyAddress!) - : undefined; - - return new ProxyAgent({ - ca: tryGetCACert(options.caBundlePath), - getProxyForUrl, - }); - } - /** * Attempts to get the region from a number of sources and falls back to us-east-1 if no region can be found, * as is done in the AWS CLI. @@ -147,7 +140,7 @@ export class AwsCliCompatible { * 3. IMDS instance identity region from the Metadata Service. * 4. us-east-1 */ - public static async region(maybeProfile?: string): Promise { + public async region(maybeProfile?: string): Promise { const defaultRegion = 'us-east-1'; const profile = maybeProfile || process.env.AWS_PROFILE || process.env.AWS_DEFAULT_PROFILE || 'default'; @@ -156,69 +149,94 @@ export class AwsCliCompatible { process.env.AMAZON_REGION || process.env.AWS_DEFAULT_REGION || process.env.AMAZON_DEFAULT_REGION || - (await getRegionFromIni(profile)) || - (await regionFromMetadataService()); + (await this.getRegionFromIni(profile)) || + (await this.regionFromMetadataService()); if (!region) { const usedProfile = !profile ? '' : ` (profile: "${profile}")`; - debug( + await this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg( `Unable to determine AWS region from environment or AWS configuration${usedProfile}, defaulting to '${defaultRegion}'`, - ); + )); return defaultRegion; } return region; } -} -/** - * Looks up the region of the provided profile. If no region is present, - * it will attempt to lookup the default region. - * @param profile The profile to use to lookup the region - * @returns The region for the profile or default profile, if present. Otherwise returns undefined. - */ -async function getRegionFromIni(profile: string): Promise { - const sharedFiles = await loadSharedConfigFiles({ ignoreCache: true }); + /** + * The MetadataService class will attempt to fetch the instance identity document from + * IMDSv2 first, and then will attempt v1 as a fallback. + * + * If this fails, we will use us-east-1 as the region so no error should be thrown. + * @returns The region for the instance identity + */ + private async regionFromMetadataService() { + await this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg('Looking up AWS region in the EC2 Instance Metadata Service (IMDS).')); + try { + const metadataService = new MetadataService({ + httpOptions: { + timeout: 1000, + }, + }); - // Priority: - // - // credentials come before config because aws-cli v1 behaves like that. - // - // 1. profile-region-in-credentials - // 2. profile-region-in-config - // 3. default-region-in-credentials - // 4. default-region-in-config + await metadataService.fetchMetadataToken(); + const document = await metadataService.request('/latest/dynamic/instance-identity/document', {}); + return JSON.parse(document).region; + } catch (e) { + await this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg(`Unable to retrieve AWS region from IMDS: ${e}`)); + } + } - return getRegionFromIniFile(profile, sharedFiles.credentialsFile) - ?? getRegionFromIniFile(profile, sharedFiles.configFile) - ?? getRegionFromIniFile('default', sharedFiles.credentialsFile) - ?? getRegionFromIniFile('default', sharedFiles.configFile); -} + /** + * Looks up the region of the provided profile. If no region is present, + * it will attempt to lookup the default region. + * @param profile The profile to use to lookup the region + * @returns The region for the profile or default profile, if present. Otherwise returns undefined. + */ + private async getRegionFromIni(profile: string): Promise { + const sharedFiles = await loadSharedConfigFiles({ ignoreCache: true }); -function getRegionFromIniFile(profile: string, data?: any) { - return data?.[profile]?.region; -} + // Priority: + // + // credentials come before config because aws-cli v1 behaves like that. + // + // 1. profile-region-in-credentials + // 2. profile-region-in-config + // 3. default-region-in-credentials + // 4. default-region-in-config -function tryGetCACert(bundlePath?: string) { - const path = bundlePath || caBundlePathFromEnvironment(); - if (path) { - debug('Using CA bundle path: %s', path); - return readIfPossible(path); + return this.getRegionFromIniFile(profile, sharedFiles.credentialsFile) + ?? this.getRegionFromIniFile(profile, sharedFiles.configFile) + ?? this.getRegionFromIniFile('default', sharedFiles.credentialsFile) + ?? this.getRegionFromIniFile('default', sharedFiles.configFile); } - return undefined; -} -/** - * Find and return a CA certificate bundle path to be passed into the SDK. - */ -function caBundlePathFromEnvironment(): string | undefined { - if (process.env.aws_ca_bundle) { - return process.env.aws_ca_bundle; + private getRegionFromIniFile(profile: string, data?: any) { + return data?.[profile]?.region; } - if (process.env.AWS_CA_BUNDLE) { - return process.env.AWS_CA_BUNDLE; + + /** + * Ask user for MFA token for given serial + * + * Result is send to callback function for SDK to authorize the request + */ + private async tokenCodeFn(serialArn: string): Promise { + const debugFn = (msg: string, ...args: any[]) => this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg(format(msg, ...args))); + await debugFn('Require MFA token for serial ARN', serialArn); + try { + const token: string = await promptly.prompt(`MFA token for ${serialArn}: `, { + trim: true, + default: '', + }); + await debugFn('Successfully got MFA token from user'); + return token; + } catch (err: any) { + await debugFn('Failed to get MFA token', err); + const e = new AuthenticationError(`Error fetching MFA token: ${err.message ?? err}`); + e.name = 'SharedIniFileCredentialsProviderFailure'; + throw e; + } } - return undefined; } /** @@ -245,54 +263,8 @@ function shouldPrioritizeEnv() { return false; } -/** - * The MetadataService class will attempt to fetch the instance identity document from - * IMDSv2 first, and then will attempt v1 as a fallback. - * - * If this fails, we will use us-east-1 as the region so no error should be thrown. - * @returns The region for the instance identity - */ -async function regionFromMetadataService() { - debug('Looking up AWS region in the EC2 Instance Metadata Service (IMDS).'); - try { - const metadataService = new MetadataService({ - httpOptions: { - timeout: 1000, - }, - }); - - await metadataService.fetchMetadataToken(); - const document = await metadataService.request('/latest/dynamic/instance-identity/document', {}); - return JSON.parse(document).region; - } catch (e) { - debug(`Unable to retrieve AWS region from IMDS: ${e}`); - } -} - export interface CredentialChainOptions { readonly profile?: string; readonly httpOptions?: SdkHttpOptions; readonly logger?: Logger; } - -/** - * Ask user for MFA token for given serial - * - * Result is send to callback function for SDK to authorize the request - */ -async function tokenCodeFn(serialArn: string): Promise { - debug('Require MFA token for serial ARN', serialArn); - try { - const token: string = await promptly.prompt(`MFA token for ${serialArn}: `, { - trim: true, - default: '', - }); - debug('Successfully got MFA token from user'); - return token; - } catch (err: any) { - debug('Failed to get MFA token', err); - const e = new AuthenticationError(`Error fetching MFA token: ${err.message ?? err}`); - e.name = 'SharedIniFileCredentialsProviderFailure'; - throw e; - } -} diff --git a/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts b/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts index 474c07164..09f5029f1 100644 --- a/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts +++ b/packages/aws-cdk/lib/api/aws-auth/credential-plugins.ts @@ -3,10 +3,10 @@ import type { CredentialProviderSource, ForReading, ForWriting, PluginProviderRe import type { AwsCredentialIdentity, AwsCredentialIdentityProvider } from '@smithy/types'; import { credentialsAboutToExpire, makeCachingProvider } from './provider-caching'; import { AuthenticationError } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api'; -import { debug, warning } from '../../logging'; +import { IO, type IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; import { formatErrorMessage } from '../../util'; import type { Mode } from '../plugin/mode'; -import { PluginHost } from '../plugin/plugin'; +import type { PluginHost } from '../plugin/plugin'; /** * Cache for credential providers. @@ -23,9 +23,11 @@ import { PluginHost } from '../plugin/plugin'; export class CredentialPlugins { private readonly cache: { [key: string]: PluginCredentialsFetchResult | undefined } = {}; private readonly host: PluginHost; + private readonly ioHelper: IoHelper; - constructor(host?: PluginHost) { - this.host = host ?? PluginHost.instance; + constructor(host: PluginHost, ioHelper: IoHelper) { + this.host = host; + this.ioHelper = ioHelper; } public async fetchCredentialsFor(awsAccountId: string, mode: Mode): Promise { @@ -49,12 +51,12 @@ export class CredentialPlugins { available = await source.isAvailable(); } catch (e: any) { // This shouldn't happen, but let's guard against it anyway - warning(`Uncaught exception in ${source.name}: ${formatErrorMessage(e)}`); + await this.ioHelper.notify(IO.CDK_TOOLKIT_W0100.msg(`Uncaught exception in ${source.name}: ${formatErrorMessage(e)}`)); available = false; } if (!available) { - debug('Credentials source %s is not available, ignoring it.', source.name); + await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`Credentials source ${source.name} is not available, ignoring it.`)); continue; } triedSources.push(source); @@ -63,13 +65,13 @@ export class CredentialPlugins { canProvide = await source.canProvideCredentials(awsAccountId); } catch (e: any) { // This shouldn't happen, but let's guard against it anyway - warning(`Uncaught exception in ${source.name}: ${formatErrorMessage(e)}`); + await this.ioHelper.notify(IO.CDK_TOOLKIT_W0100.msg(`Uncaught exception in ${source.name}: ${formatErrorMessage(e)}`)); canProvide = false; } if (!canProvide) { continue; } - debug(`Using ${source.name} credentials for account ${awsAccountId}`); + await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg(`Using ${source.name} credentials for account ${awsAccountId}`)); return { credentials: await v3ProviderFromPlugin(() => source.getProvider(awsAccountId, mode as ForReading | ForWriting, { diff --git a/packages/aws-cdk/lib/api/aws-auth/index.ts b/packages/aws-cdk/lib/api/aws-auth/index.ts index 114414547..602b17bac 100644 --- a/packages/aws-cdk/lib/api/aws-auth/index.ts +++ b/packages/aws-cdk/lib/api/aws-auth/index.ts @@ -1,3 +1,4 @@ export * from './sdk'; export * from './sdk-provider'; export * from './sdk-logger'; +export * from './proxy-agent'; diff --git a/packages/aws-cdk/lib/api/aws-auth/proxy-agent.ts b/packages/aws-cdk/lib/api/aws-auth/proxy-agent.ts new file mode 100644 index 000000000..6392828cb --- /dev/null +++ b/packages/aws-cdk/lib/api/aws-auth/proxy-agent.ts @@ -0,0 +1,56 @@ +import * as fs from 'fs-extra'; +import { ProxyAgent } from 'proxy-agent'; +import type { SdkHttpOptions } from './sdk-provider'; +import { IO, type IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; + +export class ProxyAgentProvider { + private readonly ioHelper: IoHelper; + + public constructor(ioHelper: IoHelper) { + this.ioHelper = ioHelper; + } + + public async create(options: SdkHttpOptions) { + // Force it to use the proxy provided through the command line. + // Otherwise, let the ProxyAgent auto-detect the proxy using environment variables. + const getProxyForUrl = options.proxyAddress != null + ? () => Promise.resolve(options.proxyAddress!) + : undefined; + + return new ProxyAgent({ + ca: await this.tryGetCACert(options.caBundlePath), + getProxyForUrl, + }); + } + + private async tryGetCACert(bundlePath?: string) { + const path = bundlePath || this.caBundlePathFromEnvironment(); + if (path) { + await this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg(`Using CA bundle path: ${path}`)); + try { + if (!fs.pathExistsSync(path)) { + return undefined; + } + return fs.readFileSync(path, { encoding: 'utf-8' }); + } catch (e: any) { + await this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg(String(e))); + return undefined; + } + } + return undefined; + } + + /** + * Find and return a CA certificate bundle path to be passed into the SDK. + */ + private caBundlePathFromEnvironment(): string | undefined { + if (process.env.aws_ca_bundle) { + return process.env.aws_ca_bundle; + } + if (process.env.AWS_CA_BUNDLE) { + return process.env.AWS_CA_BUNDLE; + } + return undefined; + } +} + diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-logger.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-logger.ts index a330c5863..55a9b9866 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-logger.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-logger.ts @@ -11,8 +11,11 @@ export class SdkToCliLogger implements Logger { this.ioHelper = ioHelper; } - private notify(level: 'debug' | 'info' | 'warn' | 'error', ...content: any[]) { - void this.ioHelper.notify(IO.CDK_SDK_I0000.msg(format('[SDK %s] %s', level, formatSdkLoggerContent(content)))); + private notify(level: 'info' | 'warn' | 'error', ...content: any[]) { + void this.ioHelper.notify(IO.CDK_SDK_I0100.msg(format('[SDK %s] %s', level, formatSdkLoggerContent(content)), { + sdkLevel: level, + content, + })); } public trace(..._content: any[]) { diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts index db1cf712b..eb54bb073 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts @@ -13,9 +13,9 @@ import { makeCachingProvider } from './provider-caching'; import { SDK } from './sdk'; import { callTrace, traceMemberMethods } from './tracing'; import { AuthenticationError } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api'; -import { debug, warning } from '../../logging'; +import { IO, type IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; import { formatErrorMessage } from '../../util'; -import { Mode } from '../plugin/mode'; +import { Mode, PluginHost } from '../plugin'; export type AssumeRoleAdditionalOptions = Partial>; @@ -23,6 +23,11 @@ export type AssumeRoleAdditionalOptions = Partial { return cached(this, CACHED_ACCOUNT, async () => { try { - return await new SDK(this.defaultCredentialProvider, this.defaultRegion, this.requestHandler, this.logger).currentAccount(); + return await new SDK(this.defaultCredentialProvider, this.defaultRegion, this.requestHandler, this.ioHelper, this.logger).currentAccount(); } catch (e: any) { // Treat 'ExpiredToken' specially. This is a common situation that people may find themselves in, and // they are complaining about if we fail 'cdk synth' on them. We loudly complain in order to show that // the current situation is probably undesirable, but we don't fail. if (e.name === 'ExpiredToken') { - warning( + await this.ioHelper.notify(IO.DEFAULT_SDK_WARN.msg( 'There are expired AWS credentials in your environment. The CDK app will synth without current account information.', - ); + )); return undefined; } - debug(`Unable to determine the default AWS account (${e.name}): ${formatErrorMessage(e)}`); + await this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg(`Unable to determine the default AWS account (${e.name}): ${formatErrorMessage(e)}`)); return undefined; } }); @@ -347,7 +356,7 @@ export class SdkProvider { additionalOptions?: AssumeRoleAdditionalOptions, region?: string, ): Promise { - debug(`Assuming role '${roleArn}'.`); + await this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg(`Assuming role '${roleArn}'.`)); region = region ?? this.defaultRegion; @@ -375,13 +384,13 @@ export class SdkProvider { // Call the provider at least once here, to catch an error if it occurs await credentials(); - return new SDK(credentials, region, this.requestHandler, this.logger); + return new SDK(credentials, region, this.requestHandler, this.ioHelper, this.logger); } catch (err: any) { if (err.name === 'ExpiredToken') { throw err; } - debug(`Assuming role failed: ${err.message}`); + await this.ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg(`Assuming role failed: ${err.message}`)); throw new AuthenticationError( [ 'Could not assume role in target account', diff --git a/packages/aws-cdk/lib/api/aws-auth/sdk.ts b/packages/aws-cdk/lib/api/aws-auth/sdk.ts index da1dc46c4..39e6814ef 100644 --- a/packages/aws-cdk/lib/api/aws-auth/sdk.ts +++ b/packages/aws-cdk/lib/api/aws-auth/sdk.ts @@ -347,7 +347,7 @@ import type { Account } from './sdk-provider'; import { traceMemberMethods } from './tracing'; import { defaultCliUserAgent } from './user-agent'; import { AuthenticationError } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api'; -import { debug } from '../../logging'; +import { IO, type IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; import { formatErrorMessage } from '../../util'; export interface S3ClientOptions { @@ -555,14 +555,14 @@ export interface IStepFunctionsClient { */ @traceMemberMethods export class SDK { - private static readonly accountCache = new AccountAccessKeyCache(); - public readonly currentRegion: string; public readonly config: ConfigurationOptions; protected readonly logger?: Logger; + private readonly accountCache; + /** * STS is used to check credential validity, don't do too many retries. */ @@ -577,12 +577,21 @@ export class SDK { */ private _credentialsValidated = false; + /** + * A function to create debug messages + */ + private readonly debug: (msg: string) => Promise; + constructor( private readonly credProvider: AwsCredentialIdentityProvider, region: string, requestHandler: NodeHttpHandlerOptions, + ioHelper: IoHelper, logger?: Logger, ) { + const debugFn = async (msg: string) => ioHelper.notify(IO.DEFAULT_SDK_DEBUG.msg(msg)); + this.accountCache = new AccountAccessKeyCache(AccountAccessKeyCache.DEFAULT_PATH, debugFn); + this.debug = debugFn; this.config = { region, credentials: credProvider, @@ -992,9 +1001,9 @@ export class SDK { public async currentAccount(): Promise { return cachedAsync(this, CURRENT_ACCOUNT_KEY, async () => { const creds = await this.credProvider(); - return SDK.accountCache.fetch(creds.accessKeyId, async () => { + return this.accountCache.fetch(creds.accessKeyId, async () => { // if we don't have one, resolve from STS and store in cache. - debug('Looking up default account ID from STS'); + await this.debug('Looking up default account ID from STS'); const client = new STSClient({ ...this.config, retryStrategy: this.stsRetryStrategy, @@ -1006,7 +1015,7 @@ export class SDK { if (!accountId) { throw new AuthenticationError("STS didn't return an account ID"); } - debug('Default account ID:', accountId); + await this.debug(`Default account ID: ${accountId}`); // Save another STS call later if this one already succeeded this._credentialsValidated = true; diff --git a/packages/aws-cdk/lib/api/aws-auth/util.ts b/packages/aws-cdk/lib/api/aws-auth/util.ts index b5c6f57b7..3e0a4403e 100644 --- a/packages/aws-cdk/lib/api/aws-auth/util.ts +++ b/packages/aws-cdk/lib/api/aws-auth/util.ts @@ -1,5 +1,4 @@ import * as fs from 'fs-extra'; -import { debug } from '../../logging'; /** * Read a file if it exists, or return undefined @@ -13,7 +12,6 @@ export function readIfPossible(filename: string): string | undefined { } return fs.readFileSync(filename, { encoding: 'utf-8' }); } catch (e: any) { - debug(e); return undefined; } } diff --git a/packages/aws-cdk/lib/cli/cli.ts b/packages/aws-cdk/lib/cli/cli.ts index 19641dba2..71a71b9dd 100644 --- a/packages/aws-cdk/lib/cli/cli.ts +++ b/packages/aws-cdk/lib/cli/cli.ts @@ -120,7 +120,9 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise; private readonly includeAcknowlegded: boolean; private readonly httpOptions: SdkHttpOptions; + private readonly ioHelper: IoHelper; private readonly ioMessages: IoDefaultMessages; private data: Set = new Set(); @@ -317,7 +319,8 @@ export class Notices { this.includeAcknowlegded = props.includeAcknowledged ?? false; this.output = props.output ?? 'cdk.out'; this.httpOptions = props.httpOptions ?? {}; - this.ioMessages = new IoDefaultMessages(asIoHelper(props.ioHost, 'notices' as any /* forcing a CliAction to a ToolkitAction */)); + this.ioHelper = asIoHelper(props.ioHost, 'notices' as any /* forcing a CliAction to a ToolkitAction */); + this.ioMessages = new IoDefaultMessages(this.ioHelper); } /** @@ -343,7 +346,7 @@ export class Notices { */ public async refresh(options: NoticesRefreshOptions = {}) { try { - const underlyingDataSource = options.dataSource ?? new WebsiteNoticeDataSource(this.ioMessages, this.httpOptions); + const underlyingDataSource = options.dataSource ?? new WebsiteNoticeDataSource(this.ioHelper, this.httpOptions); const dataSource = new CachedDataSource(this.ioMessages, CACHE_FILE_PATH, underlyingDataSource, options.force ?? false); const notices = await dataSource.fetch(); this.data = new Set(this.includeAcknowlegded ? notices : notices.filter(n => !this.acknowledgedIssueNumbers.has(n.issueNumber))); @@ -485,13 +488,18 @@ export interface NoticeDataSource { export class WebsiteNoticeDataSource implements NoticeDataSource { private readonly options: SdkHttpOptions; - constructor(private readonly ioMessages: IoDefaultMessages, options: SdkHttpOptions = {}) { + constructor(private readonly ioHelper: IoHelper, options: SdkHttpOptions = {}) { this.options = options; } - fetch(): Promise { + async fetch(): Promise { const timeout = 3000; - return new Promise((resolve, reject) => { + + const options: RequestOptions = { + agent: await new ProxyAgentProvider(this.ioHelper).create(this.options), + }; + + const notices = await new Promise((resolve, reject) => { let req: ClientRequest | undefined; let timer = setTimeout(() => { @@ -502,10 +510,6 @@ export class WebsiteNoticeDataSource implements NoticeDataSource { timer.unref(); - const options: RequestOptions = { - agent: AwsCliCompatible.proxyAgent(this.options), - }; - try { req = https.get('https://cli.cdk.dev-tools.aws.dev/notices.json', options, @@ -522,7 +526,6 @@ export class WebsiteNoticeDataSource implements NoticeDataSource { if (!data) { throw new ToolkitError("'notices' key is missing"); } - this.ioMessages.debug('Notices refreshed'); resolve(data ?? []); } catch (e: any) { reject(new ToolkitError(`Failed to parse notices: ${formatErrorMessage(e)}`)); @@ -540,6 +543,9 @@ export class WebsiteNoticeDataSource implements NoticeDataSource { reject(new ToolkitError(`HTTPS 'get' call threw an error: ${formatErrorMessage(e)}`)); } }); + + await this.ioHelper.notify(IO.DEFAULT_TOOLKIT_DEBUG.msg('Notices refreshed')); + return notices; } } diff --git a/packages/aws-cdk/test/api/_helpers/hotswap-test-setup.ts b/packages/aws-cdk/test/api/_helpers/hotswap-test-setup.ts index 5965761b3..4f8e9b107 100644 --- a/packages/aws-cdk/test/api/_helpers/hotswap-test-setup.ts +++ b/packages/aws-cdk/test/api/_helpers/hotswap-test-setup.ts @@ -145,6 +145,6 @@ export class HotswapMockSdkProvider extends MockSdkProvider { hotswapPropertyOverrides?: HotswapPropertyOverrides, ): Promise { let hotswapProps = hotswapPropertyOverrides || new HotswapPropertyOverrides(); - return deployments.tryHotswapDeployment(this, asIoHelper(ioHost, 'deploy'), assetParams, currentCfnStack, stackArtifact, hotswapMode, hotswapProps); + return deployments.tryHotswapDeployment(this, asIoHelper(ioHost, 'deploy'), assetParams, currentCfnStack, stackArtifact, hotswapMode as any, hotswapProps); } } diff --git a/packages/aws-cdk/test/api/aws-auth/account-cache.test.ts b/packages/aws-cdk/test/api/aws-auth/account-cache.test.ts index a61f817f8..a1846cd1f 100644 --- a/packages/aws-cdk/test/api/aws-auth/account-cache.test.ts +++ b/packages/aws-cdk/test/api/aws-auth/account-cache.test.ts @@ -4,13 +4,15 @@ import * as fs from 'fs-extra'; import { AccountAccessKeyCache } from '../../../lib/api/aws-auth/account-cache'; import { withMocked } from '../../_helpers/as-mock'; +const noOp = async () => {}; + async function makeCache() { const dir = await fs.mkdtemp('/tmp/account-cache-test'); const file = path.join(dir, 'cache.json'); return { cacheDir: dir, cacheFile: file, - cache: new AccountAccessKeyCache(file), + cache: new AccountAccessKeyCache(file, noOp), }; } @@ -20,7 +22,7 @@ async function nukeCache(cacheDir: string) { test('default account cache uses CDK_HOME', () => { process.env.CDK_HOME = '/banana'; - const cache = new AccountAccessKeyCache(); + const cache = new AccountAccessKeyCache(undefined, noOp); expect((cache as any).cacheFile).toContain('/banana/'); }); @@ -32,7 +34,7 @@ test('account cache does not fail when given a nonwritable directory', async () // Have to do this because mkdirs has 2 overloads and it confuses TypeScript (mkdirs as unknown as jest.Mock, [any]>).mockRejectedValue(accessError); - const cache = new AccountAccessKeyCache('/abc/xyz'); + const cache = new AccountAccessKeyCache('/abc/xyz', noOp); await cache.fetch('xyz', () => Promise.resolve({ accountId: 'asdf', partition: 'swa' })); // No exception @@ -58,7 +60,7 @@ test('put(k,v) and then get(k)', async () => { expect(await cache.get('key')).toEqual({ accountId: 'value', partition: 'aws' }); // create another cache instance on the same file, should still work - const cache2 = new AccountAccessKeyCache(cacheFile); + const cache2 = new AccountAccessKeyCache(cacheFile, noOp); expect(await cache2.get('boo')).toEqual({ accountId: 'bar', partition: 'aws' }); // whitebox: read the file @@ -93,7 +95,7 @@ test(`cache is nuked if it exceeds ${AccountAccessKeyCache.MAX_ENTRIES} entries` } // verify all values are on disk - const otherCache = new AccountAccessKeyCache(cacheFile); + const otherCache = new AccountAccessKeyCache(cacheFile, noOp); for (let i = 0; i < AccountAccessKeyCache.MAX_ENTRIES; ++i) { expect(await otherCache.get(`key${i}`)).toEqual({ accountId: `value${i}`, partition: 'aws' }); } @@ -131,7 +133,7 @@ describe('using cache file', () => { }); test('uses the resolver when the file cannot be read', async () => { - const cache = new AccountAccessKeyCache('/foo/account-cache.json'); + const cache = new AccountAccessKeyCache('/foo/account-cache.json', noOp); const account = { accountId: 'abc', partition: 'aws', @@ -153,7 +155,7 @@ describe('using cache file', () => { })}`, }); - const cache = new AccountAccessKeyCache(bockfs.path('/foo/account-cache.json')); + const cache = new AccountAccessKeyCache(bockfs.path('/foo/account-cache.json'), noOp); const result = await cache.fetch('abcdef', () => Promise.resolve({ accountId: 'xyz', partition: 'aws', diff --git a/packages/aws-cdk/test/api/aws-auth/awscli-compatible.test.ts b/packages/aws-cdk/test/api/aws-auth/awscli-compatible.test.ts index 2b20597f3..588a2fa14 100644 --- a/packages/aws-cdk/test/api/aws-auth/awscli-compatible.test.ts +++ b/packages/aws-cdk/test/api/aws-auth/awscli-compatible.test.ts @@ -2,6 +2,10 @@ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs-extra'; import { AwsCliCompatible } from '../../../lib/api/aws-auth/awscli-compatible'; +import { TestIoHost } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; + +const ioHost = new TestIoHost(); +const ioHelper = ioHost.asHelper('sdk'); describe('AwsCliCompatible.region', () => { @@ -235,7 +239,7 @@ async function region(opts: { process.env.AWS_SHARED_CREDENTIALS_FILE = credentialsPath; } - return await AwsCliCompatible.region(opts.profile); + return await new AwsCliCompatible(ioHelper).region(opts.profile); } finally { fs.removeSync(workdir); @@ -253,7 +257,7 @@ describe('Session token', () => { delete process.env.AWS_SESSION_TOKEN; delete process.env.AMAZON_SESSION_TOKEN; - await AwsCliCompatible.credentialChainBuilder(); + await new AwsCliCompatible(ioHelper).credentialChainBuilder(); expect(process.env.AWS_SESSION_TOKEN).toBeUndefined(); }); @@ -262,7 +266,7 @@ describe('Session token', () => { process.env.AWS_SESSION_TOKEN = 'aaa'; delete process.env.AMAZON_SESSION_TOKEN; - await AwsCliCompatible.credentialChainBuilder(); + await new AwsCliCompatible(ioHelper).credentialChainBuilder(); expect(process.env.AWS_SESSION_TOKEN).toEqual('aaa'); }); @@ -271,7 +275,7 @@ describe('Session token', () => { delete process.env.AWS_SESSION_TOKEN; process.env.AMAZON_SESSION_TOKEN = 'aaa'; - await AwsCliCompatible.credentialChainBuilder(); + await new AwsCliCompatible(ioHelper).credentialChainBuilder(); expect(process.env.AWS_SESSION_TOKEN).toEqual('aaa'); }); @@ -280,7 +284,7 @@ describe('Session token', () => { process.env.AWS_SESSION_TOKEN = 'aaa'; process.env.AMAZON_SESSION_TOKEN = 'bbb'; - await AwsCliCompatible.credentialChainBuilder(); + await new AwsCliCompatible(ioHelper).credentialChainBuilder(); expect(process.env.AWS_SESSION_TOKEN).toEqual('aaa'); }); diff --git a/packages/aws-cdk/test/api/aws-auth/credential-plugins.test.ts b/packages/aws-cdk/test/api/aws-auth/credential-plugins.test.ts index de0c48aae..a30329435 100644 --- a/packages/aws-cdk/test/api/aws-auth/credential-plugins.test.ts +++ b/packages/aws-cdk/test/api/aws-auth/credential-plugins.test.ts @@ -2,6 +2,10 @@ import type { PluginProviderResult, SDKv2CompatibleCredentials } from '@aws-cdk/ import { CredentialPlugins } from '../../../lib/api/aws-auth/credential-plugins'; import { PluginHost } from '../../../lib/api/plugin'; import { Mode } from '../../../lib/api/plugin/mode'; +import { TestIoHost } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; + +const ioHost = new TestIoHost(); +const ioHelper = ioHost.asHelper('deploy'); test('returns credential from plugin', async () => { // GIVEN @@ -28,7 +32,7 @@ test('returns credential from plugin', async () => { }, }); - const plugins = new CredentialPlugins(); + const plugins = new CredentialPlugins(host, ioHelper); // WHEN const pluginCredentials = await plugins.fetchCredentialsFor('aaa', Mode.ForReading); diff --git a/packages/aws-cdk/test/api/aws-auth/sdk-provider.test.ts b/packages/aws-cdk/test/api/aws-auth/sdk-provider.test.ts index 204a4f289..e2ad6da56 100644 --- a/packages/aws-cdk/test/api/aws-auth/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/aws-auth/sdk-provider.test.ts @@ -24,9 +24,9 @@ import { AwsCliCompatible } from '../../../lib/api/aws-auth/awscli-compatible'; import { defaultCliUserAgent } from '../../../lib/api/aws-auth/user-agent'; import { PluginHost } from '../../../lib/api/plugin'; import { Mode } from '../../../lib/api/plugin/mode'; -import { CliIoHost } from '../../../lib/cli/io-host'; -import { withMocked } from '../../_helpers/as-mock'; +import { instanceMockFrom, withMocked } from '../../_helpers/as-mock'; import { undoAllSdkMocks } from '../../util/mock-sdk'; +import { TestIoHost } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; // As part of the imports above we import `mock-sdk.ts` which automatically mocks // all SDK clients. We don't want that for this test suite, so undo it. @@ -52,6 +52,9 @@ jest.mock('@aws-sdk/ec2-metadata-service', () => { let uid: string; let pluginQueried: boolean; +const ioHost = new TestIoHost('trace'); +const ioHelper = ioHost.asHelper('sdk'); + beforeEach(() => { // Cache busters! // We prefix everything with UUIDs because: @@ -61,7 +64,8 @@ beforeEach(() => { uid = `(${uuid.v4()})`; pluginQueried = false; - CliIoHost.instance().logLevel = 'trace'; + ioHost.notifySpy.mockClear(); + ioHost.requestSpy.mockClear(); PluginHost.instance.credentialProviderSources.splice(0); PluginHost.instance.credentialProviderSources.push({ @@ -89,7 +93,6 @@ beforeEach(() => { }); afterEach(() => { - CliIoHost.instance().logLevel = 'info'; bockfs.restore(); jest.restoreAllMocks(); }); @@ -160,7 +163,7 @@ describe('with intercepted network calls', () => { const error = new Error('Expired Token'); error.name = 'ExpiredToken'; const identityProvider = () => Promise.reject(error); - const provider = new SdkProvider(identityProvider, 'rgn'); + const provider = new SdkProvider(identityProvider, 'rgn', {}, ioHelper); const creds = await provider.baseCredentialsPartition({ ...env(account), region: 'rgn' }, Mode.ForReading); expect(creds).toBeUndefined(); @@ -168,7 +171,7 @@ describe('with intercepted network calls', () => { test('throws if profile credentials are not for the right account', async () => { // WHEN - jest.spyOn(AwsCliCompatible, 'region').mockResolvedValue('us-east-123'); + jest.spyOn(AwsCliCompatible.prototype, 'region').mockResolvedValueOnce('us-east-123'); prepareCreds({ fakeSts, config: { @@ -322,7 +325,7 @@ describe('with intercepted network calls', () => { // The profile is not passed explicitly. Should be picked from the environment variable process.env.AWS_PROFILE = 'mfa-role'; // Awaiting to make sure the environment variable is only deleted after it's used - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ logger: console }); + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ioHelper, logger: console }); delete process.env.AWS_PROFILE; return Promise.resolve(provider); }), @@ -826,7 +829,7 @@ function isProfileRole(x: ProfileUser | ProfileRole): x is ProfileRole { } async function providerFromProfile(profile: string | undefined) { - return SdkProvider.withAwsCliCompatibleDefaults({ profile, logger: console }); + return SdkProvider.withAwsCliCompatibleDefaults({ ioHelper, profile, logger: console }); } async function exerciseCredentials(provider: SdkProvider, e: cxapi.Environment, mode: Mode = Mode.ForReading, diff --git a/packages/aws-cdk/test/api/plugin/credential-plugin.test.ts b/packages/aws-cdk/test/api/plugin/credential-plugin.test.ts index 0db7916c0..68487457b 100644 --- a/packages/aws-cdk/test/api/plugin/credential-plugin.test.ts +++ b/packages/aws-cdk/test/api/plugin/credential-plugin.test.ts @@ -3,15 +3,19 @@ import { CredentialPlugins } from '../../../lib/api/aws-auth/credential-plugins' import { credentialsAboutToExpire } from '../../../lib/api/aws-auth/provider-caching'; import { Mode } from '../../../lib/api/plugin/mode'; import { PluginHost, markTesting } from '../../../lib/api/plugin/plugin'; +import { TestIoHost } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private/testing'; markTesting(); let host: PluginHost; let credentialPlugins: CredentialPlugins; +const ioHost = new TestIoHost(); +const ioHelper = ioHost.asHelper('deploy'); + beforeEach(() => { host = new PluginHost(); - credentialPlugins = new CredentialPlugins(host); + credentialPlugins = new CredentialPlugins(host, ioHelper); jest.resetModules(); jest.useFakeTimers(); }); diff --git a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts index b4d96d971..7a801793d 100644 --- a/packages/aws-cdk/test/cli/cdk-toolkit.test.ts +++ b/packages/aws-cdk/test/cli/cdk-toolkit.test.ts @@ -73,7 +73,6 @@ import { Bootstrapper, type BootstrapSource } from '../../lib/api/bootstrap'; import { DeployStackResult, SuccessfulDeployStackResult, - Template, Deployments, DeployStackOptions, DestroyStackOptions, @@ -99,6 +98,7 @@ import { } from '../util/mock-sdk'; import { asIoHelper } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; import { StackActivityProgress } from '../../lib/commands/deploy'; +import { Template } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api'; markTesting(); diff --git a/packages/aws-cdk/test/context-providers/amis.test.ts b/packages/aws-cdk/test/context-providers/amis.test.ts index f18cccf45..05f55b112 100644 --- a/packages/aws-cdk/test/context-providers/amis.test.ts +++ b/packages/aws-cdk/test/context-providers/amis.test.ts @@ -3,10 +3,11 @@ import { DescribeImagesCommand } from '@aws-sdk/client-ec2'; import { SDK, SdkForEnvironment } from '../../lib/api'; import { AmiContextProviderPlugin } from '../../lib/context-providers/ami'; import { FAKE_CREDENTIAL_CHAIN, MockSdkProvider, mockEC2Client } from '../util/mock-sdk'; +import { TestIoHost } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; const mockSDK = new (class extends MockSdkProvider { public forEnvironment(): Promise { - return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}), didAssumeRole: false }); + return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}, new TestIoHost().asHelper("deploy")), didAssumeRole: false }); } })(); diff --git a/packages/aws-cdk/test/context-providers/availability-zones.test.ts b/packages/aws-cdk/test/context-providers/availability-zones.test.ts index 6151c58bd..b9f357aeb 100644 --- a/packages/aws-cdk/test/context-providers/availability-zones.test.ts +++ b/packages/aws-cdk/test/context-providers/availability-zones.test.ts @@ -2,10 +2,11 @@ import { DescribeAvailabilityZonesCommand } from '@aws-sdk/client-ec2'; import { SDK, SdkForEnvironment } from '../../lib/api'; import { AZContextProviderPlugin } from '../../lib/context-providers/availability-zones'; import { FAKE_CREDENTIAL_CHAIN, mockEC2Client, MockSdkProvider } from '../util/mock-sdk'; +import { TestIoHost } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; const mockSDK = new (class extends MockSdkProvider { public forEnvironment(): Promise { - return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}), didAssumeRole: false }); + return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}, new TestIoHost().asHelper("deploy")), didAssumeRole: false }); } })(); diff --git a/packages/aws-cdk/test/context-providers/endpoint-service-availability-zones.test.ts b/packages/aws-cdk/test/context-providers/endpoint-service-availability-zones.test.ts index 3493d941b..396f7c533 100644 --- a/packages/aws-cdk/test/context-providers/endpoint-service-availability-zones.test.ts +++ b/packages/aws-cdk/test/context-providers/endpoint-service-availability-zones.test.ts @@ -4,10 +4,11 @@ import { EndpointServiceAZContextProviderPlugin, } from '../../lib/context-providers/endpoint-service-availability-zones'; import { FAKE_CREDENTIAL_CHAIN, mockEC2Client, MockSdkProvider } from '../util/mock-sdk'; +import { TestIoHost } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; const mockSDK = new (class extends MockSdkProvider { public forEnvironment(): Promise { - return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}), didAssumeRole: false }); + return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}, new TestIoHost().asHelper("deploy")), didAssumeRole: false }); } })(); diff --git a/packages/aws-cdk/test/context-providers/hosted-zones.test.ts b/packages/aws-cdk/test/context-providers/hosted-zones.test.ts index 2912c1bf8..6b630d1c4 100644 --- a/packages/aws-cdk/test/context-providers/hosted-zones.test.ts +++ b/packages/aws-cdk/test/context-providers/hosted-zones.test.ts @@ -2,10 +2,11 @@ import { GetHostedZoneCommand, ListHostedZonesByNameCommand } from '@aws-sdk/cli import { SDK, SdkForEnvironment } from '../../lib/api'; import { HostedZoneContextProviderPlugin } from '../../lib/context-providers/hosted-zones'; import { FAKE_CREDENTIAL_CHAIN, mockRoute53Client, MockSdkProvider } from '../util/mock-sdk'; +import { TestIoHost } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; const mockSDK = new (class extends MockSdkProvider { public forEnvironment(): Promise { - return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}), didAssumeRole: false }); + return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}, new TestIoHost().asHelper("deploy")), didAssumeRole: false }); } })(); diff --git a/packages/aws-cdk/test/context-providers/load-balancers.test.ts b/packages/aws-cdk/test/context-providers/load-balancers.test.ts index e167717c8..9fd0a21e5 100644 --- a/packages/aws-cdk/test/context-providers/load-balancers.test.ts +++ b/packages/aws-cdk/test/context-providers/load-balancers.test.ts @@ -15,10 +15,11 @@ import { mockElasticLoadBalancingV2Client, restoreSdkMocksToDefault, } from '../util/mock-sdk'; +import { TestIoHost } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; const mockSDK = new (class extends MockSdkProvider { public forEnvironment(): Promise { - return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}), didAssumeRole: false }); + return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}, new TestIoHost().asHelper("deploy")), didAssumeRole: false }); } })(); diff --git a/packages/aws-cdk/test/context-providers/security-groups.test.ts b/packages/aws-cdk/test/context-providers/security-groups.test.ts index 562a08682..65b000b18 100644 --- a/packages/aws-cdk/test/context-providers/security-groups.test.ts +++ b/packages/aws-cdk/test/context-providers/security-groups.test.ts @@ -2,10 +2,11 @@ import { DescribeSecurityGroupsCommand } from '@aws-sdk/client-ec2'; import { SDK, type SdkForEnvironment } from '../../lib/api'; import { hasAllTrafficEgress, SecurityGroupContextProviderPlugin } from '../../lib/context-providers/security-groups'; import { FAKE_CREDENTIAL_CHAIN, MockSdkProvider, mockEC2Client, restoreSdkMocksToDefault } from '../util/mock-sdk'; +import { TestIoHost } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; const mockSDK = new (class extends MockSdkProvider { public forEnvironment(): Promise { - return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}), didAssumeRole: false }); + return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}, new TestIoHost().asHelper("deploy")), didAssumeRole: false }); } })(); diff --git a/packages/aws-cdk/test/context-providers/ssm-parameters.test.ts b/packages/aws-cdk/test/context-providers/ssm-parameters.test.ts index e3de3c6d7..1ffb6e66f 100644 --- a/packages/aws-cdk/test/context-providers/ssm-parameters.test.ts +++ b/packages/aws-cdk/test/context-providers/ssm-parameters.test.ts @@ -2,10 +2,11 @@ import { GetParameterCommand } from '@aws-sdk/client-ssm'; import { SDK, SdkForEnvironment } from '../../lib/api'; import { SSMContextProviderPlugin } from '../../lib/context-providers/ssm-parameters'; import { FAKE_CREDENTIAL_CHAIN, MockSdkProvider, mockSSMClient, restoreSdkMocksToDefault } from '../util/mock-sdk'; +import { TestIoHost } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; const mockSDK = new (class extends MockSdkProvider { public forEnvironment(): Promise { - return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}), didAssumeRole: false }); + return Promise.resolve({ sdk: new SDK(FAKE_CREDENTIAL_CHAIN, mockSDK.defaultRegion, {}, new TestIoHost().asHelper("deploy")), didAssumeRole: false }); } })(); diff --git a/packages/aws-cdk/test/notices.test.ts b/packages/aws-cdk/test/notices.test.ts index ad3ad7567..8bf66940f 100644 --- a/packages/aws-cdk/test/notices.test.ts +++ b/packages/aws-cdk/test/notices.test.ts @@ -167,7 +167,8 @@ const NOTICE_FOR_APIGATEWAYV2_CFN_STAGE = { }; const ioHost = new FakeIoHost(); -const ioHostEmitter = new IoDefaultMessages(asIoHelper(ioHost, 'notices' as any)); +const ioHelper = asIoHelper(ioHost, 'notices' as any) +const ioHostEmitter = new IoDefaultMessages(ioHelper); const noticesFilter = new NoticesFilter(ioHostEmitter); beforeEach(() => { @@ -448,7 +449,7 @@ function parseTestComponent(x: string): Component { describe(WebsiteNoticeDataSource, () => { - const dataSource = new WebsiteNoticeDataSource(ioHostEmitter); + const dataSource = new WebsiteNoticeDataSource(ioHelper); test('returns data when download succeeds', async () => { const result = await mockCall(200, { diff --git a/packages/aws-cdk/test/util/mock-sdk.ts b/packages/aws-cdk/test/util/mock-sdk.ts index 8d03af586..904d56ace 100644 --- a/packages/aws-cdk/test/util/mock-sdk.ts +++ b/packages/aws-cdk/test/util/mock-sdk.ts @@ -24,6 +24,7 @@ import { mockClient } from 'aws-sdk-client-mock'; import { type Account } from 'cdk-assets'; import { SDK, SdkProvider } from '../../lib/api/aws-auth'; import { CloudFormationStack } from '../../lib/api/cloudformation'; +import { TestIoHost } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; export const FAKE_CREDENTIALS: AwsCredentialIdentity = { accessKeyId: 'ACCESS', @@ -143,7 +144,7 @@ export const setDefaultSTSMocks = () => { */ export class MockSdkProvider extends SdkProvider { constructor() { - super(FAKE_CREDENTIAL_CHAIN, 'bermuda-triangle-1337'); + super(FAKE_CREDENTIAL_CHAIN, 'bermuda-triangle-1337', {}, new TestIoHost().asHelper('sdk')); } public defaultAccount(): Promise { @@ -159,7 +160,7 @@ export class MockSdkProvider extends SdkProvider { */ export class MockSdk extends SDK { constructor() { - super(FAKE_CREDENTIAL_CHAIN, 'bermuda-triangle-1337', {}); + super(FAKE_CREDENTIAL_CHAIN, 'bermuda-triangle-1337', {}, new TestIoHost().asHelper('sdk')); } }