diff --git a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/types.ts b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/types.ts index 909caef8a..dfd223ec1 100644 --- a/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/types.ts +++ b/packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/types.ts @@ -98,3 +98,10 @@ export interface ConfirmationRequest { */ readonly concurrency?: number; } + +export interface ContextProviderMessageSource { + /** + * The name of the context provider sending the message + */ + readonly provider: string; +} 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 f7650c29e..438db2738 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 @@ -12,7 +12,7 @@ import type { StackRollbackProgress } from '../payloads/rollback'; import type { SdkTrace } from '../payloads/sdk-trace'; import type { StackActivity, StackMonitoringControlEvent } from '../payloads/stack-activity'; import type { StackSelectionDetails } from '../payloads/synth'; -import type { AssemblyData, ConfirmationRequest, Duration, ErrorPayload, StackAndAssemblyData } from '../payloads/types'; +import type { AssemblyData, ConfirmationRequest, ContextProviderMessageSource, Duration, ErrorPayload, StackAndAssemblyData } from '../payloads/types'; import type { FileWatchEvent, WatchSettings } from '../payloads/watch'; /** @@ -379,6 +379,10 @@ export const IO = { code: 'CDK_ASSEMBLY_I0000', description: 'Default trace messages emitted from Cloud Assembly operations', }), + DEFAULT_ASSEMBLY_DEBUG: make.debug({ + code: 'CDK_ASSEMBLY_I0000', + description: 'Default debug messages emitted from Cloud Assembly operations', + }), CDK_ASSEMBLY_I0010: make.debug({ code: 'CDK_ASSEMBLY_I0010', @@ -430,6 +434,17 @@ export const IO = { description: 'Indicates the use of a pre-synthesized cloud assembly directory', }), + CDK_ASSEMBLY_I0300: make.info({ + code: 'CDK_ASSEMBLY_I0300', + description: 'An info message emitted by a Context Provider', + interface: 'ContextProviderMessageSource', + }), + CDK_ASSEMBLY_I0301: make.debug({ + code: 'CDK_ASSEMBLY_I0301', + description: 'A debug message emitted by a Context Provider', + interface: 'ContextProviderMessageSource', + }), + // Assembly Annotations CDK_ASSEMBLY_I9999: make.info({ code: 'CDK_ASSEMBLY_I9999', diff --git a/packages/@aws-cdk/toolkit-lib/docs/message-registry.md b/packages/@aws-cdk/toolkit-lib/docs/message-registry.md index b45e3823a..351c5bb14 100644 --- a/packages/@aws-cdk/toolkit-lib/docs/message-registry.md +++ b/packages/@aws-cdk/toolkit-lib/docs/message-registry.md @@ -74,6 +74,7 @@ group: Documents | `CDK_TOOLKIT_E0101` | A notice that is marked as an error | `error` | n/a | | `CDK_TOOLKIT_I0101` | A notice that is marked as informational | `info` | n/a | | `CDK_ASSEMBLY_I0000` | Default trace messages emitted from Cloud Assembly operations | `trace` | n/a | +| `CDK_ASSEMBLY_I0000` | Default debug messages emitted from Cloud Assembly operations | `debug` | n/a | | `CDK_ASSEMBLY_I0010` | Generic environment preparation debug messages | `debug` | n/a | | `CDK_ASSEMBLY_W0010` | Emitted if the found framework version does not support context overflow | `warn` | n/a | | `CDK_ASSEMBLY_I0042` | Writing updated context | `debug` | {@link UpdatedContext} | @@ -85,6 +86,8 @@ group: Documents | `CDK_ASSEMBLY_I1003` | Cloud assembly output finished | `info` | n/a | | `CDK_ASSEMBLY_E1111` | Incompatible CDK CLI version. Upgrade needed. | `error` | {@link ErrorPayload} | | `CDK_ASSEMBLY_I0150` | Indicates the use of a pre-synthesized cloud assembly directory | `debug` | n/a | +| `CDK_ASSEMBLY_I0300` | An info message emitted by a Context Provider | `info` | {@link ContextProviderMessageSource} | +| `CDK_ASSEMBLY_I0301` | A debug message emitted by a Context Provider | `debug` | {@link ContextProviderMessageSource} | | `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) | diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/private/context-aware-source.ts b/packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/private/context-aware-source.ts index ddc608203..3759fce42 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/private/context-aware-source.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/private/context-aware-source.ts @@ -89,6 +89,7 @@ export class ContextAwareCloudAssembly implements ICloudAssemblySource { assembly.manifest.missing, this.context, this.props.services.sdkProvider, + this.ioHelper, ); // Cache the new context to disk diff --git a/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts b/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts index 31bfc9c64..c45d51ca3 100644 --- a/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts +++ b/packages/aws-cdk/lib/api/cxapp/cloud-executable.ts @@ -91,7 +91,8 @@ export class CloudExecutable { await contextproviders.provideContextValues( assembly.manifest.missing, this.props.configuration.context, - this.props.sdkProvider); + this.props.sdkProvider, + ); // Cache the new context to disk await this.props.configuration.saveContext(); diff --git a/packages/aws-cdk/lib/cli/io-host/cli-io-host.ts b/packages/aws-cdk/lib/cli/io-host/cli-io-host.ts index dd6db7993..6708f9463 100644 --- a/packages/aws-cdk/lib/cli/io-host/cli-io-host.ts +++ b/packages/aws-cdk/lib/cli/io-host/cli-io-host.ts @@ -4,6 +4,7 @@ import * as chalk from 'chalk'; import * as promptly from 'promptly'; import { ToolkitError } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api'; import type { IIoHost, IoMessage, IoMessageCode, IoMessageLevel, IoRequest, ToolkitAction } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api'; +import type { IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; import { asIoHelper, IO, IoDefaultMessages, isMessageRelevantForLevel } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; import { StackActivityProgress } from '../../commands/deploy'; import { CurrentActivityPrinter, HistoryActivityPrinter } from '../activity-printer'; @@ -205,8 +206,11 @@ export class CliIoHost implements IIoHost { } public get defaults() { - const helper = asIoHelper(this, this.currentAction as any); - return new IoDefaultMessages(helper); + return new IoDefaultMessages(this.asIoHelper()); + } + + public asIoHelper(): IoHelper { + return asIoHelper(this, this.currentAction as any); } /** diff --git a/packages/aws-cdk/lib/context-providers/ami.ts b/packages/aws-cdk/lib/context-providers/ami.ts index 559b16b57..e9ff3292b 100644 --- a/packages/aws-cdk/lib/context-providers/ami.ts +++ b/packages/aws-cdk/lib/context-providers/ami.ts @@ -1,14 +1,14 @@ import type { AmiContextQuery } from '@aws-cdk/cloud-assembly-schema'; +import type { IContextProviderMessages } from '.'; import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api'; import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import type { ContextProviderPlugin } from '../api/plugin'; -import { debug, info } from '../logging'; /** * Plugin to search AMIs for the current account */ export class AmiContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SdkProvider) { + constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) { } public async getValue(args: AmiContextQuery) { @@ -17,8 +17,8 @@ export class AmiContextProviderPlugin implements ContextProviderPlugin { // Normally we'd do this only as 'debug', but searching AMIs typically takes dozens // of seconds, so be little more verbose about it so users know what is going on. - info(`Searching for AMI in ${account}:${region}`); - debug(`AMI search parameters: ${JSON.stringify(args)}`); + await this.io.info(`Searching for AMI in ${account}:${region}`); + await this.io.debug(`AMI search parameters: ${JSON.stringify(args)}`); const ec2 = (await initContextProviderSdk(this.aws, args)).ec2(); const response = await ec2.describeImages({ @@ -40,7 +40,7 @@ export class AmiContextProviderPlugin implements ContextProviderPlugin { // but since we only care about the relative values that is okay. images.sort(descending((i) => Date.parse(i.CreationDate || '1970'))); - debug(`Selected image '${images[0].ImageId}' created at '${images[0].CreationDate}'`); + await this.io.debug(`Selected image '${images[0].ImageId}' created at '${images[0].CreationDate}'`); return images[0].ImageId!; } } diff --git a/packages/aws-cdk/lib/context-providers/availability-zones.ts b/packages/aws-cdk/lib/context-providers/availability-zones.ts index 7b2d4cc72..83bd520f0 100644 --- a/packages/aws-cdk/lib/context-providers/availability-zones.ts +++ b/packages/aws-cdk/lib/context-providers/availability-zones.ts @@ -1,20 +1,20 @@ import type { AvailabilityZonesContextQuery } from '@aws-cdk/cloud-assembly-schema'; import type { AvailabilityZone } from '@aws-sdk/client-ec2'; +import type { IContextProviderMessages } from '.'; import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import type { ContextProviderPlugin } from '../api/plugin'; -import { debug } from '../logging'; /** * Plugin to retrieve the Availability Zones for the current account */ export class AZContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SdkProvider) { + constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) { } public async getValue(args: AvailabilityZonesContextQuery) { const region = args.region; const account = args.account; - debug(`Reading AZs for ${account}:${region}`); + await this.io.debug(`Reading AZs for ${account}:${region}`); const ec2 = (await initContextProviderSdk(this.aws, args)).ec2(); const response = await ec2.describeAvailabilityZones({}); if (!response.AvailabilityZones) { diff --git a/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts b/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts index 6e0320dee..533c04db8 100644 --- a/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts +++ b/packages/aws-cdk/lib/context-providers/endpoint-service-availability-zones.ts @@ -1,20 +1,20 @@ import type { EndpointServiceAvailabilityZonesContextQuery } from '@aws-cdk/cloud-assembly-schema'; +import type { IContextProviderMessages } from '.'; import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import type { ContextProviderPlugin } from '../api/plugin'; -import { debug } from '../logging'; /** * Plugin to retrieve the Availability Zones for an endpoint service */ export class EndpointServiceAZContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SdkProvider) { + constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) { } public async getValue(args: EndpointServiceAvailabilityZonesContextQuery) { const region = args.region; const account = args.account; const serviceName = args.serviceName; - debug(`Reading AZs for ${account}:${region}:${serviceName}`); + await this.io.debug(`Reading AZs for ${account}:${region}:${serviceName}`); const ec2 = (await initContextProviderSdk(this.aws, args)).ec2(); const response = await ec2.describeVpcEndpointServices({ ServiceNames: [serviceName], @@ -22,11 +22,11 @@ export class EndpointServiceAZContextProviderPlugin implements ContextProviderPl // expect a service in the response if (!response.ServiceDetails || response.ServiceDetails.length === 0) { - debug(`Could not retrieve service details for ${account}:${region}:${serviceName}`); + await this.io.debug(`Could not retrieve service details for ${account}:${region}:${serviceName}`); return []; } const azs = response.ServiceDetails[0].AvailabilityZones; - debug(`Endpoint service ${account}:${region}:${serviceName} is available in availability zones ${azs}`); + await this.io.debug(`Endpoint service ${account}:${region}:${serviceName} is available in availability zones ${azs}`); return azs; } } diff --git a/packages/aws-cdk/lib/context-providers/hosted-zones.ts b/packages/aws-cdk/lib/context-providers/hosted-zones.ts index 604556ce1..3c09ad944 100644 --- a/packages/aws-cdk/lib/context-providers/hosted-zones.ts +++ b/packages/aws-cdk/lib/context-providers/hosted-zones.ts @@ -1,13 +1,13 @@ import type { HostedZoneContextQuery } from '@aws-cdk/cloud-assembly-schema'; import type { HostedZone } from '@aws-sdk/client-route-53'; +import type { IContextProviderMessages } from '.'; import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api'; import type { IRoute53Client } from '../api'; import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import type { ContextProviderPlugin } from '../api/plugin'; -import { debug } from '../logging'; export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SdkProvider) { + constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) { } public async getValue(args: HostedZoneContextQuery): Promise { @@ -17,7 +17,7 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { throw new ContextProviderError(`HostedZoneProvider requires domainName property to be set in ${args}`); } const domainName = args.domainName; - debug(`Reading hosted zone ${account}:${region}:${domainName}`); + await this.io.debug(`Reading hosted zone ${account}:${region}:${domainName}`); const r53 = (await initContextProviderSdk(this.aws, args)).route53(); const response = await r53.listHostedZonesByName({ DNSName: domainName }); if (!response.HostedZones) { @@ -42,9 +42,9 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { ): Promise { let candidates: HostedZone[] = []; const domainName = props.domainName.endsWith('.') ? props.domainName : `${props.domainName}.`; - debug(`Found the following zones ${JSON.stringify(zones)}`); + await this.io.debug(`Found the following zones ${JSON.stringify(zones)}`); candidates = zones.filter((zone) => zone.Name === domainName); - debug(`Found the following matched name zones ${JSON.stringify(candidates)}`); + await this.io.debug(`Found the following matched name zones ${JSON.stringify(candidates)}`); if (props.privateZone) { candidates = candidates.filter((zone) => zone.Config && zone.Config.PrivateZone); } else { @@ -55,7 +55,7 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin { for (const zone of candidates) { const data = await r53.getHostedZone({ Id: zone.Id }); if (!data.VPCs) { - debug(`Expected VPC for private zone but no VPC found ${zone.Id}`); + await this.io.debug(`Expected VPC for private zone but no VPC found ${zone.Id}`); continue; } if (data.VPCs.map((vpc) => vpc.VPCId).includes(props.vpcId)) { diff --git a/packages/aws-cdk/lib/context-providers/index.ts b/packages/aws-cdk/lib/context-providers/index.ts index b9f560da2..b17c44131 100644 --- a/packages/aws-cdk/lib/context-providers/index.ts +++ b/packages/aws-cdk/lib/context-providers/index.ts @@ -11,27 +11,70 @@ import { SecurityGroupContextProviderPlugin } from './security-groups'; import { SSMContextProviderPlugin } from './ssm-parameters'; import { VpcNetworkContextProviderPlugin } from './vpcs'; import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api'; +import type { IoHelper } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; +import { IO } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private'; import type { SdkProvider } from '../api'; import type { Context } from '../api/context'; import { TRANSIENT_CONTEXT_KEY } from '../api/context'; import { replaceEnvPlaceholders } from '../api/environment'; import { PluginHost } from '../api/plugin'; import type { ContextProviderPlugin } from '../api/plugin/context-provider-plugin'; -import { debug } from '../logging'; +import { CliIoHost } from '../cli/io-host'; import { formatErrorMessage } from '../util'; -export type ContextProviderFactory = ((sdk: SdkProvider) => ContextProviderPlugin); -export type ProviderMap = {[name: string]: ContextProviderFactory}; +type ContextProviderFactory = ((sdk: SdkProvider, io: IContextProviderMessages) => ContextProviderPlugin); +type ProviderMap = {[name: string]: ContextProviderFactory}; const PLUGIN_PROVIDER_PREFIX = 'plugin'; +export interface IContextProviderMessages { + /** + * A message that is presented to users in normal mode of operation. + * + * Should be used sparingly. The Context Provider framework already provides useful output by default. + * This can be uses in exceptionally situations, e.g. if a lookup call is expected to take a long time. + */ + info(message: string): Promise; + + /** + * A message that helps users debugging the context provider. + * + * Should be used in most cases to note on current action. + */ + debug(message: string): Promise; +} + +class ContextProviderMessages implements IContextProviderMessages { + private readonly ioHelper: IoHelper; + private readonly providerName: string; + + public constructor(ioHelper: IoHelper, providerName: string) { + this.ioHelper = ioHelper; + this.providerName = providerName; + } + + public async info(message: string): Promise { + return this.ioHelper.notify(IO.CDK_ASSEMBLY_I0300.msg(message, { + provider: this.providerName, + })); + } + + public async debug(message: string): Promise { + return this.ioHelper.notify(IO.CDK_ASSEMBLY_I0301.msg(message, { + provider: this.providerName, + })); + } +} + /** * Iterate over the list of missing context values and invoke the appropriate providers from the map to retrieve them */ export async function provideContextValues( missingValues: cxschema.MissingContext[], context: Context, - sdk: SdkProvider) { + sdk: SdkProvider, + ioHelper?: IoHelper, +) { for (const missingContext of missingValues) { const key = missingContext.key; @@ -55,7 +98,8 @@ export async function provideContextValues( } } - const provider = factory(sdk); + ioHelper = ioHelper ?? CliIoHost.instance().asIoHelper(); + const provider = factory(sdk, new ContextProviderMessages(ioHelper, providerName)); let value; try { @@ -78,7 +122,7 @@ export async function provideContextValues( value = { [cxapi.PROVIDER_ERROR_KEY]: formatErrorMessage(e), [TRANSIENT_CONTEXT_KEY]: true }; } context.set(key, value); - debug(`Setting "${key}" context to ${JSON.stringify(value)}`); + await ioHelper.notify(IO.DEFAULT_ASSEMBLY_DEBUG.msg(`Setting "${key}" context to ${JSON.stringify(value)}`)); } } @@ -110,15 +154,15 @@ export function registerContextProviderFactory(name: string, provider: ContextPr } const availableContextProviders: ProviderMap = { - [cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER]: (s) => new AZContextProviderPlugin(s), - [cxschema.ContextProvider.SSM_PARAMETER_PROVIDER]: (s) => new SSMContextProviderPlugin(s), - [cxschema.ContextProvider.HOSTED_ZONE_PROVIDER]: (s) => new HostedZoneContextProviderPlugin(s), - [cxschema.ContextProvider.VPC_PROVIDER]: (s) => new VpcNetworkContextProviderPlugin(s), - [cxschema.ContextProvider.AMI_PROVIDER]: (s) => new AmiContextProviderPlugin(s), - [cxschema.ContextProvider.ENDPOINT_SERVICE_AVAILABILITY_ZONE_PROVIDER]: (s) => new EndpointServiceAZContextProviderPlugin(s), + [cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER]: (s, io) => new AZContextProviderPlugin(s, io), + [cxschema.ContextProvider.SSM_PARAMETER_PROVIDER]: (s, io) => new SSMContextProviderPlugin(s, io), + [cxschema.ContextProvider.HOSTED_ZONE_PROVIDER]: (s, io) => new HostedZoneContextProviderPlugin(s, io), + [cxschema.ContextProvider.VPC_PROVIDER]: (s, io) => new VpcNetworkContextProviderPlugin(s, io), + [cxschema.ContextProvider.AMI_PROVIDER]: (s, io) => new AmiContextProviderPlugin(s, io), + [cxschema.ContextProvider.ENDPOINT_SERVICE_AVAILABILITY_ZONE_PROVIDER]: (s, io) => new EndpointServiceAZContextProviderPlugin(s, io), [cxschema.ContextProvider.SECURITY_GROUP_PROVIDER]: (s) => new SecurityGroupContextProviderPlugin(s), [cxschema.ContextProvider.LOAD_BALANCER_PROVIDER]: (s) => new LoadBalancerContextProviderPlugin(s), [cxschema.ContextProvider.LOAD_BALANCER_LISTENER_PROVIDER]: (s) => new LoadBalancerListenerContextProviderPlugin(s), - [cxschema.ContextProvider.KEY_PROVIDER]: (s) => new KeyContextProviderPlugin(s), + [cxschema.ContextProvider.KEY_PROVIDER]: (s, io) => new KeyContextProviderPlugin(s, io), [cxschema.ContextProvider.CC_API_PROVIDER]: (s) => new CcApiContextProviderPlugin(s), }; diff --git a/packages/aws-cdk/lib/context-providers/keys.ts b/packages/aws-cdk/lib/context-providers/keys.ts index 5ea0cda2a..a95d2a55c 100644 --- a/packages/aws-cdk/lib/context-providers/keys.ts +++ b/packages/aws-cdk/lib/context-providers/keys.ts @@ -1,14 +1,14 @@ import type { KeyContextQuery } from '@aws-cdk/cloud-assembly-schema'; import type { KeyContextResponse } from '@aws-cdk/cx-api'; import type { AliasListEntry, ListAliasesCommandOutput } from '@aws-sdk/client-kms'; +import type { IContextProviderMessages } from '.'; import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api'; import type { IKMSClient } from '../api'; import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import type { ContextProviderPlugin } from '../api/plugin'; -import { debug } from '../logging'; export class KeyContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SdkProvider) { + constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) { } public async getValue(args: KeyContextQuery) { @@ -21,7 +21,7 @@ export class KeyContextProviderPlugin implements ContextProviderPlugin { // TODO: use paginator function private async findKey(kms: IKMSClient, args: KeyContextQuery): Promise { - debug(`Listing keys in ${args.account}:${args.region}`); + await this.io.debug(`Listing keys in ${args.account}:${args.region}`); let response: ListAliasesCommandOutput; let nextMarker: string | undefined; @@ -54,7 +54,7 @@ export class KeyContextProviderPlugin implements ContextProviderPlugin { throw new ContextProviderError(`Could not find any key with alias named ${args.aliasName}`); } - debug(`Key found ${alias.TargetKeyId}`); + await this.io.debug(`Key found ${alias.TargetKeyId}`); return { keyId: alias.TargetKeyId, diff --git a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts index e3fd18ce0..f05ce8e72 100644 --- a/packages/aws-cdk/lib/context-providers/ssm-parameters.ts +++ b/packages/aws-cdk/lib/context-providers/ssm-parameters.ts @@ -1,15 +1,15 @@ import type { SSMParameterContextQuery } from '@aws-cdk/cloud-assembly-schema'; import type { GetParameterCommandOutput } from '@aws-sdk/client-ssm'; +import type { IContextProviderMessages } from '.'; import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api'; import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import type { ContextProviderPlugin } from '../api/plugin'; -import { debug } from '../logging'; /** * Plugin to read arbitrary SSM parameter names */ export class SSMContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SdkProvider) { + constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) { } public async getValue(args: SSMParameterContextQuery) { @@ -20,7 +20,7 @@ export class SSMContextProviderPlugin implements ContextProviderPlugin { throw new ContextProviderError('parameterName must be provided in props for SSMContextProviderPlugin'); } const parameterName = args.parameterName; - debug(`Reading SSM parameter ${account}:${region}:${parameterName}`); + await this.io.debug(`Reading SSM parameter ${account}:${region}:${parameterName}`); const response = await this.getSsmParameterValue(args); const parameterNotFound: boolean = !response.Parameter || response.Parameter.Value === undefined; diff --git a/packages/aws-cdk/lib/context-providers/vpcs.ts b/packages/aws-cdk/lib/context-providers/vpcs.ts index 2dd7d312e..b149985e4 100644 --- a/packages/aws-cdk/lib/context-providers/vpcs.ts +++ b/packages/aws-cdk/lib/context-providers/vpcs.ts @@ -1,13 +1,14 @@ import type { VpcContextQuery } from '@aws-cdk/cloud-assembly-schema'; import { type VpcContextResponse, type VpcSubnetGroup, VpcSubnetGroupType } from '@aws-cdk/cx-api'; import type { Filter, RouteTable, Tag, Vpc } from '@aws-sdk/client-ec2'; +import type { IContextProviderMessages } from '.'; import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api'; import type { IEC2Client } from '../api'; import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider'; import type { ContextProviderPlugin } from '../api/plugin'; -import { debug } from '../logging'; + export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin { - constructor(private readonly aws: SdkProvider) { + constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) { } public async getValue(args: VpcContextQuery) { @@ -22,7 +23,7 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin { // Build request filter (map { Name -> Value } to list of [{ Name, Values }]) const filters: Filter[] = Object.entries(args.filter).map(([tag, value]) => ({ Name: tag, Values: [value] })); - debug(`Listing VPCs in ${args.account}:${args.region}`); + await this.io.debug(`Listing VPCs in ${args.account}:${args.region}`); const response = await ec2.describeVpcs({ Filters: filters }); const vpcs = response.Vpcs || []; @@ -39,7 +40,7 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin { private async readVpcProps(ec2: IEC2Client, vpc: Vpc, args: VpcContextQuery): Promise { const vpcId = vpc.VpcId!; - debug(`Describing VPC ${vpcId}`); + await this.io.debug(`Describing VPC ${vpcId}`); const filters = { Filters: [{ Name: 'vpc-id', Values: [vpcId] }] }; diff --git a/packages/aws-cdk/test/context-providers/amis.test.ts b/packages/aws-cdk/test/context-providers/amis.test.ts index 06e64b138..f18cccf45 100644 --- a/packages/aws-cdk/test/context-providers/amis.test.ts +++ b/packages/aws-cdk/test/context-providers/amis.test.ts @@ -10,6 +10,16 @@ const mockSDK = new (class extends MockSdkProvider { } })(); +const mockMsg = { + debug: jest.fn(), + info: jest.fn(), +}; + +beforeEach(() => { + mockMsg.debug.mockClear(); + mockMsg.info.mockClear(); +}); + test('calls DescribeImages on the request', async () => { // GIVEN mockEC2Client.on(DescribeImagesCommand).resolves({ @@ -17,7 +27,7 @@ test('calls DescribeImages on the request', async () => { }); // WHEN - await new AmiContextProviderPlugin(mockSDK).getValue({ + await new AmiContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '1234', region: 'asdf', owners: ['some-owner'], @@ -54,7 +64,7 @@ test('returns the most recent AMI matching the criteria', async () => { }); // WHEN - const result = await new AmiContextProviderPlugin(mockSDK).getValue({ + const result = await new AmiContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '1234', region: 'asdf', filters: {}, diff --git a/packages/aws-cdk/test/context-providers/asymmetric-vpcs.test.ts b/packages/aws-cdk/test/context-providers/asymmetric-vpcs.test.ts index 3892ebc77..b66083332 100644 --- a/packages/aws-cdk/test/context-providers/asymmetric-vpcs.test.ts +++ b/packages/aws-cdk/test/context-providers/asymmetric-vpcs.test.ts @@ -7,7 +7,15 @@ import { import { VpcNetworkContextProviderPlugin } from '../../lib/context-providers/vpcs'; import { MockSdkProvider, mockEC2Client, restoreSdkMocksToDefault } from '../util/mock-sdk'; +const mockMsg = { + debug: jest.fn(), + info: jest.fn(), +}; + beforeEach(() => { + mockMsg.debug.mockClear(); + mockMsg.info.mockClear(); + restoreSdkMocksToDefault(); mockEC2Client .on(DescribeVpcsCommand) @@ -84,7 +92,7 @@ test('looks up the requested (symmetric) VPC', async () => { }) .on(DescribeVpnGatewaysCommand) .resolves({ VpnGateways: [{ VpnGatewayId: 'gw-abcdef' }] }); - const result = await new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + const result = await new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, @@ -144,7 +152,7 @@ test('looks up the requested (symmetric) VPC', async () => { test('throws when no such VPC is found', async () => { mockEC2Client.on(DescribeVpcsCommand).resolves({}); await expect( - new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, @@ -164,7 +172,7 @@ test('throws when multiple VPCs are found', async () => { // WHEN await expect( - new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, @@ -241,7 +249,7 @@ test('uses the VPC main route table when a subnet has no specific association', VpnGateways: [{ VpnGatewayId: 'gw-abcdef' }], }); - const result = await new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + const result = await new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, @@ -330,7 +338,7 @@ test('Recognize public subnet by route table', async () => { }); // WHEN - const result = await new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + const result = await new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, @@ -396,7 +404,7 @@ test('Recognize isolated subnet by route table', async () => { }); // WHEN - const result = await new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + const result = await new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, @@ -458,7 +466,7 @@ test('Recognize private subnet by route table', async () => { }); // WHEN - const result = await new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + const result = await new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, @@ -551,7 +559,7 @@ test('works for asymmetric subnets (not spanning the same Availability Zones)', }); // WHEN - const result = await new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + const result = await new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, @@ -667,7 +675,7 @@ test('allows specifying the subnet group name tag', async () => { ], }); - const result = await new VpcNetworkContextProviderPlugin(mockSDK).getValue({ + const result = await new VpcNetworkContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '123456789012', region: 'us-east-1', filter: { foo: 'bar' }, 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 ba4e7689d..6151c58bd 100644 --- a/packages/aws-cdk/test/context-providers/availability-zones.test.ts +++ b/packages/aws-cdk/test/context-providers/availability-zones.test.ts @@ -9,6 +9,16 @@ const mockSDK = new (class extends MockSdkProvider { } })(); +const mockMsg = { + debug: jest.fn(), + info: jest.fn(), +}; + +beforeEach(() => { + mockMsg.debug.mockClear(); + mockMsg.info.mockClear(); +}); + test('empty array as result when response has no AZs', async () => { // GIVEN mockEC2Client.on(DescribeAvailabilityZonesCommand).resolves({ @@ -16,7 +26,7 @@ test('empty array as result when response has no AZs', async () => { }); // WHEN - const azs = await new AZContextProviderPlugin(mockSDK).getValue({ + const azs = await new AZContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '1234', region: 'asdf', }); @@ -35,7 +45,7 @@ test('returns AZs', async () => { }); // WHEN - const azs = await new AZContextProviderPlugin(mockSDK).getValue({ + const azs = await new AZContextProviderPlugin(mockSDK, mockMsg).getValue({ account: '1234', region: 'asdf', }); 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 718acbc37..3493d941b 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 @@ -11,12 +11,22 @@ const mockSDK = new (class extends MockSdkProvider { } })(); +const mockMsg = { + debug: jest.fn(), + info: jest.fn(), +}; + +beforeEach(() => { + mockMsg.debug.mockClear(); + mockMsg.info.mockClear(); +}); + test('empty result when service details cannot be retrieved', async () => { // GIVEN mockEC2Client.on(DescribeVpcEndpointServicesCommand).resolves({}); // WHEN - const result = await new EndpointServiceAZContextProviderPlugin(mockSDK).getValue({ + const result = await new EndpointServiceAZContextProviderPlugin(mockSDK, mockMsg).getValue({ serviceName: 'svc', account: 'foo', region: 'rgn', @@ -34,7 +44,7 @@ test('returns availability zones', async () => { }); // WHEN - const result = await new EndpointServiceAZContextProviderPlugin(mockSDK).getValue({ + const result = await new EndpointServiceAZContextProviderPlugin(mockSDK, mockMsg).getValue({ serviceName: 'svc', account: 'foo', region: 'rgn', 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 d03971064..2912c1bf8 100644 --- a/packages/aws-cdk/test/context-providers/hosted-zones.test.ts +++ b/packages/aws-cdk/test/context-providers/hosted-zones.test.ts @@ -9,6 +9,16 @@ const mockSDK = new (class extends MockSdkProvider { } })(); +const mockMsg = { + debug: jest.fn(), + info: jest.fn(), +}; + +beforeEach(() => { + mockMsg.debug.mockClear(); + mockMsg.info.mockClear(); +}); + test('get value without private zone', async () => { // GIVEN mockRoute53Client.on(ListHostedZonesByNameCommand).resolves({ @@ -20,7 +30,7 @@ test('get value without private zone', async () => { }); // WHEN - const result = await new HostedZoneContextProviderPlugin(mockSDK).getValue({ + const result = await new HostedZoneContextProviderPlugin(mockSDK, mockMsg).getValue({ domainName: 'example.com', account: '1234', region: 'rgn', @@ -46,7 +56,7 @@ test('get value with private zone', async () => { }); // WHEN - const result = await new HostedZoneContextProviderPlugin(mockSDK).getValue({ + const result = await new HostedZoneContextProviderPlugin(mockSDK, mockMsg).getValue({ domainName: 'example.com', account: '1234', region: 'rgn', @@ -76,7 +86,7 @@ test('get value with private zone and VPC not found', async () => { mockRoute53Client.on(GetHostedZoneCommand).resolves({}); // WHEN - const result = new HostedZoneContextProviderPlugin(mockSDK).getValue({ + const result = new HostedZoneContextProviderPlugin(mockSDK, mockMsg).getValue({ domainName: 'example.com', account: '1234', region: 'rgn', @@ -109,7 +119,7 @@ test('get value with private zone and VPC found', async () => { }); // WHEN - const result = await new HostedZoneContextProviderPlugin(mockSDK).getValue({ + const result = await new HostedZoneContextProviderPlugin(mockSDK, mockMsg).getValue({ domainName: 'example.com', account: '1234', region: 'rgn', diff --git a/packages/aws-cdk/test/context-providers/keys.test.ts b/packages/aws-cdk/test/context-providers/keys.test.ts index a4191e733..9f2cdd797 100644 --- a/packages/aws-cdk/test/context-providers/keys.test.ts +++ b/packages/aws-cdk/test/context-providers/keys.test.ts @@ -4,8 +4,16 @@ import { mockKMSClient, MockSdkProvider, restoreSdkMocksToDefault } from '../uti let provider: KeyContextProviderPlugin; +const mockMsg = { + debug: jest.fn(), + info: jest.fn(), +}; + beforeEach(() => { - provider = new KeyContextProviderPlugin(new MockSdkProvider()); + mockMsg.debug.mockClear(); + mockMsg.info.mockClear(); + + provider = new KeyContextProviderPlugin(new MockSdkProvider(), mockMsg); restoreSdkMocksToDefault(); }); 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 f83f17a30..e3de3c6d7 100644 --- a/packages/aws-cdk/test/context-providers/ssm-parameters.test.ts +++ b/packages/aws-cdk/test/context-providers/ssm-parameters.test.ts @@ -9,10 +9,20 @@ const mockSDK = new (class extends MockSdkProvider { } })(); +const mockMsg = { + debug: jest.fn(), + info: jest.fn(), +}; + +beforeEach(() => { + mockMsg.debug.mockClear(); + mockMsg.info.mockClear(); +}); + describe('ssmParameters', () => { test('returns value', async () => { restoreSdkMocksToDefault(); - const provider = new SSMContextProviderPlugin(mockSDK); + const provider = new SSMContextProviderPlugin(mockSDK, mockMsg); mockSSMClient.on(GetParameterCommand).resolves({ Parameter: { @@ -32,7 +42,7 @@ describe('ssmParameters', () => { test('errors when parameter is not found', async () => { restoreSdkMocksToDefault(); - const provider = new SSMContextProviderPlugin(mockSDK); + const provider = new SSMContextProviderPlugin(mockSDK, mockMsg); const notFound = new Error('Parameter not found'); notFound.name = 'ParameterNotFound'; diff --git a/packages/aws-cdk/test/context-providers/vpcs.test.ts b/packages/aws-cdk/test/context-providers/vpcs.test.ts index 84ddf88a0..bbad68d15 100644 --- a/packages/aws-cdk/test/context-providers/vpcs.test.ts +++ b/packages/aws-cdk/test/context-providers/vpcs.test.ts @@ -8,8 +8,15 @@ import { VpcNetworkContextProviderPlugin } from '../../lib/context-providers/vpc import { MockSdkProvider, mockEC2Client, restoreSdkMocksToDefault } from '../util/mock-sdk'; const mockSDK = new MockSdkProvider(); +const mockMsg = { + debug: jest.fn(), + info: jest.fn(), +}; beforeEach(() => { + mockMsg.debug.mockClear(); + mockMsg.info.mockClear(); + restoreSdkMocksToDefault(); mockEC2Client .on(DescribeVpcsCommand) @@ -73,7 +80,7 @@ beforeEach(() => { test('looks up the requested VPC', async () => { // GIVEN const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN const result = await provider.getValue({ @@ -117,7 +124,7 @@ test('looks up the requested VPC', async () => { test('throws when no such VPC is found', async () => { // GIVEN const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); mockEC2Client.on(DescribeVpcsCommand).resolves({}); // WHEN @@ -136,7 +143,7 @@ test('throws when no such VPC is found', async () => { test('throws when subnet with subnetGroupNameTag not found', async () => { // GIVEN const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN await expect( @@ -177,7 +184,7 @@ test('does not throw when subnet with subnetGroupNameTag is found', async () => ], }); const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN const result = await provider.getValue({ @@ -225,7 +232,7 @@ test('throws when multiple VPCs are found', async () => { Vpcs: [{ VpcId: 'vpc-1' }, { VpcId: 'vpc-2' }], }); const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN await expect( @@ -283,7 +290,7 @@ test('uses the VPC main route table when a subnet has no specific association', ], }); const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN const result = await provider.getValue({ @@ -364,7 +371,7 @@ test('Recognize public subnet by route table', async () => { .on(DescribeVpnGatewaysCommand) .resolves({}); const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN const result = await provider.getValue({ @@ -440,7 +447,7 @@ test('Recognize private subnet by route table with NAT Gateway', async () => { .on(DescribeVpnGatewaysCommand) .resolves({}); const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN const result = await provider.getValue({ @@ -516,7 +523,7 @@ test('Recognize private subnet by route table with Transit Gateway', async () => .on(DescribeVpnGatewaysCommand) .resolves({}); const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN const result = await provider.getValue({ @@ -580,7 +587,7 @@ test('Recognize isolated subnet by route table', async () => { .on(DescribeVpnGatewaysCommand) .resolves({}); const filter = { foo: 'bar' }; - const provider = new VpcNetworkContextProviderPlugin(mockSDK); + const provider = new VpcNetworkContextProviderPlugin(mockSDK, mockMsg); // WHEN const result = await provider.getValue({