Skip to content

Commit 289490b

Browse files
mrgraingithub-actions
and
github-actions
authored
refactor(cli): context providers use dedicated, provided messaging class (#277)
Introduces a dedicated messaging interface for Context Providers. This is a reduced interface to allow Context Providers send debug messages and in rare cases info messages. Use of `ioHelper` in the `provideContextValues` function is currently still optional until the CloudExecutable supports it as well. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license --------- Signed-off-by: github-actions <[email protected]> Co-authored-by: github-actions <[email protected]>
1 parent 556a88c commit 289490b

22 files changed

+228
-79
lines changed

packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/payloads/types.ts

+7
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,10 @@ export interface ConfirmationRequest {
9898
*/
9999
readonly concurrency?: number;
100100
}
101+
102+
export interface ContextProviderMessageSource {
103+
/**
104+
* The name of the context provider sending the message
105+
*/
106+
readonly provider: string;
107+
}

packages/@aws-cdk/tmp-toolkit-helpers/src/api/io/private/messages.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { StackRollbackProgress } from '../payloads/rollback';
1212
import type { SdkTrace } from '../payloads/sdk-trace';
1313
import type { StackActivity, StackMonitoringControlEvent } from '../payloads/stack-activity';
1414
import type { StackSelectionDetails } from '../payloads/synth';
15-
import type { AssemblyData, ConfirmationRequest, Duration, ErrorPayload, StackAndAssemblyData } from '../payloads/types';
15+
import type { AssemblyData, ConfirmationRequest, ContextProviderMessageSource, Duration, ErrorPayload, StackAndAssemblyData } from '../payloads/types';
1616
import type { FileWatchEvent, WatchSettings } from '../payloads/watch';
1717

1818
/**
@@ -379,6 +379,10 @@ export const IO = {
379379
code: 'CDK_ASSEMBLY_I0000',
380380
description: 'Default trace messages emitted from Cloud Assembly operations',
381381
}),
382+
DEFAULT_ASSEMBLY_DEBUG: make.debug({
383+
code: 'CDK_ASSEMBLY_I0000',
384+
description: 'Default debug messages emitted from Cloud Assembly operations',
385+
}),
382386

383387
CDK_ASSEMBLY_I0010: make.debug({
384388
code: 'CDK_ASSEMBLY_I0010',
@@ -430,6 +434,17 @@ export const IO = {
430434
description: 'Indicates the use of a pre-synthesized cloud assembly directory',
431435
}),
432436

437+
CDK_ASSEMBLY_I0300: make.info<ContextProviderMessageSource>({
438+
code: 'CDK_ASSEMBLY_I0300',
439+
description: 'An info message emitted by a Context Provider',
440+
interface: 'ContextProviderMessageSource',
441+
}),
442+
CDK_ASSEMBLY_I0301: make.debug<ContextProviderMessageSource>({
443+
code: 'CDK_ASSEMBLY_I0301',
444+
description: 'A debug message emitted by a Context Provider',
445+
interface: 'ContextProviderMessageSource',
446+
}),
447+
433448
// Assembly Annotations
434449
CDK_ASSEMBLY_I9999: make.info<cxapi.SynthesisMessage>({
435450
code: 'CDK_ASSEMBLY_I9999',

packages/@aws-cdk/toolkit-lib/docs/message-registry.md

+3
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ group: Documents
7474
| `CDK_TOOLKIT_E0101` | A notice that is marked as an error | `error` | n/a |
7575
| `CDK_TOOLKIT_I0101` | A notice that is marked as informational | `info` | n/a |
7676
| `CDK_ASSEMBLY_I0000` | Default trace messages emitted from Cloud Assembly operations | `trace` | n/a |
77+
| `CDK_ASSEMBLY_I0000` | Default debug messages emitted from Cloud Assembly operations | `debug` | n/a |
7778
| `CDK_ASSEMBLY_I0010` | Generic environment preparation debug messages | `debug` | n/a |
7879
| `CDK_ASSEMBLY_W0010` | Emitted if the found framework version does not support context overflow | `warn` | n/a |
7980
| `CDK_ASSEMBLY_I0042` | Writing updated context | `debug` | {@link UpdatedContext} |
@@ -85,6 +86,8 @@ group: Documents
8586
| `CDK_ASSEMBLY_I1003` | Cloud assembly output finished | `info` | n/a |
8687
| `CDK_ASSEMBLY_E1111` | Incompatible CDK CLI version. Upgrade needed. | `error` | {@link ErrorPayload} |
8788
| `CDK_ASSEMBLY_I0150` | Indicates the use of a pre-synthesized cloud assembly directory | `debug` | n/a |
89+
| `CDK_ASSEMBLY_I0300` | An info message emitted by a Context Provider | `info` | {@link ContextProviderMessageSource} |
90+
| `CDK_ASSEMBLY_I0301` | A debug message emitted by a Context Provider | `debug` | {@link ContextProviderMessageSource} |
8891
| `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) |
8992
| `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) |
9093
| `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) |

packages/@aws-cdk/toolkit-lib/lib/api/cloud-assembly/private/context-aware-source.ts

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export class ContextAwareCloudAssembly implements ICloudAssemblySource {
8989
assembly.manifest.missing,
9090
this.context,
9191
this.props.services.sdkProvider,
92+
this.ioHelper,
9293
);
9394

9495
// Cache the new context to disk

packages/aws-cdk/lib/api/cxapp/cloud-executable.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ export class CloudExecutable {
9191
await contextproviders.provideContextValues(
9292
assembly.manifest.missing,
9393
this.props.configuration.context,
94-
this.props.sdkProvider);
94+
this.props.sdkProvider,
95+
);
9596

9697
// Cache the new context to disk
9798
await this.props.configuration.saveContext();

packages/aws-cdk/lib/cli/io-host/cli-io-host.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as chalk from 'chalk';
44
import * as promptly from 'promptly';
55
import { ToolkitError } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api';
66
import type { IIoHost, IoMessage, IoMessageCode, IoMessageLevel, IoRequest, ToolkitAction } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api';
7+
import type { IoHelper } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
78
import { asIoHelper, IO, IoDefaultMessages, isMessageRelevantForLevel } from '../../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
89
import { StackActivityProgress } from '../../commands/deploy';
910
import { CurrentActivityPrinter, HistoryActivityPrinter } from '../activity-printer';
@@ -205,8 +206,11 @@ export class CliIoHost implements IIoHost {
205206
}
206207

207208
public get defaults() {
208-
const helper = asIoHelper(this, this.currentAction as any);
209-
return new IoDefaultMessages(helper);
209+
return new IoDefaultMessages(this.asIoHelper());
210+
}
211+
212+
public asIoHelper(): IoHelper {
213+
return asIoHelper(this, this.currentAction as any);
210214
}
211215

212216
/**

packages/aws-cdk/lib/context-providers/ami.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import type { AmiContextQuery } from '@aws-cdk/cloud-assembly-schema';
2+
import type { IContextProviderMessages } from '.';
23
import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api';
34
import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider';
45
import type { ContextProviderPlugin } from '../api/plugin';
5-
import { debug, info } from '../logging';
66

77
/**
88
* Plugin to search AMIs for the current account
99
*/
1010
export class AmiContextProviderPlugin implements ContextProviderPlugin {
11-
constructor(private readonly aws: SdkProvider) {
11+
constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) {
1212
}
1313

1414
public async getValue(args: AmiContextQuery) {
@@ -17,8 +17,8 @@ export class AmiContextProviderPlugin implements ContextProviderPlugin {
1717

1818
// Normally we'd do this only as 'debug', but searching AMIs typically takes dozens
1919
// of seconds, so be little more verbose about it so users know what is going on.
20-
info(`Searching for AMI in ${account}:${region}`);
21-
debug(`AMI search parameters: ${JSON.stringify(args)}`);
20+
await this.io.info(`Searching for AMI in ${account}:${region}`);
21+
await this.io.debug(`AMI search parameters: ${JSON.stringify(args)}`);
2222

2323
const ec2 = (await initContextProviderSdk(this.aws, args)).ec2();
2424
const response = await ec2.describeImages({
@@ -40,7 +40,7 @@ export class AmiContextProviderPlugin implements ContextProviderPlugin {
4040
// but since we only care about the relative values that is okay.
4141
images.sort(descending((i) => Date.parse(i.CreationDate || '1970')));
4242

43-
debug(`Selected image '${images[0].ImageId}' created at '${images[0].CreationDate}'`);
43+
await this.io.debug(`Selected image '${images[0].ImageId}' created at '${images[0].CreationDate}'`);
4444
return images[0].ImageId!;
4545
}
4646
}

packages/aws-cdk/lib/context-providers/availability-zones.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import type { AvailabilityZonesContextQuery } from '@aws-cdk/cloud-assembly-schema';
22
import type { AvailabilityZone } from '@aws-sdk/client-ec2';
3+
import type { IContextProviderMessages } from '.';
34
import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider';
45
import type { ContextProviderPlugin } from '../api/plugin';
5-
import { debug } from '../logging';
66

77
/**
88
* Plugin to retrieve the Availability Zones for the current account
99
*/
1010
export class AZContextProviderPlugin implements ContextProviderPlugin {
11-
constructor(private readonly aws: SdkProvider) {
11+
constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) {
1212
}
1313

1414
public async getValue(args: AvailabilityZonesContextQuery) {
1515
const region = args.region;
1616
const account = args.account;
17-
debug(`Reading AZs for ${account}:${region}`);
17+
await this.io.debug(`Reading AZs for ${account}:${region}`);
1818
const ec2 = (await initContextProviderSdk(this.aws, args)).ec2();
1919
const response = await ec2.describeAvailabilityZones({});
2020
if (!response.AvailabilityZones) {
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
import type { EndpointServiceAvailabilityZonesContextQuery } from '@aws-cdk/cloud-assembly-schema';
2+
import type { IContextProviderMessages } from '.';
23
import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider';
34
import type { ContextProviderPlugin } from '../api/plugin';
4-
import { debug } from '../logging';
55

66
/**
77
* Plugin to retrieve the Availability Zones for an endpoint service
88
*/
99
export class EndpointServiceAZContextProviderPlugin implements ContextProviderPlugin {
10-
constructor(private readonly aws: SdkProvider) {
10+
constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) {
1111
}
1212

1313
public async getValue(args: EndpointServiceAvailabilityZonesContextQuery) {
1414
const region = args.region;
1515
const account = args.account;
1616
const serviceName = args.serviceName;
17-
debug(`Reading AZs for ${account}:${region}:${serviceName}`);
17+
await this.io.debug(`Reading AZs for ${account}:${region}:${serviceName}`);
1818
const ec2 = (await initContextProviderSdk(this.aws, args)).ec2();
1919
const response = await ec2.describeVpcEndpointServices({
2020
ServiceNames: [serviceName],
2121
});
2222

2323
// expect a service in the response
2424
if (!response.ServiceDetails || response.ServiceDetails.length === 0) {
25-
debug(`Could not retrieve service details for ${account}:${region}:${serviceName}`);
25+
await this.io.debug(`Could not retrieve service details for ${account}:${region}:${serviceName}`);
2626
return [];
2727
}
2828
const azs = response.ServiceDetails[0].AvailabilityZones;
29-
debug(`Endpoint service ${account}:${region}:${serviceName} is available in availability zones ${azs}`);
29+
await this.io.debug(`Endpoint service ${account}:${region}:${serviceName} is available in availability zones ${azs}`);
3030
return azs;
3131
}
3232
}

packages/aws-cdk/lib/context-providers/hosted-zones.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import type { HostedZoneContextQuery } from '@aws-cdk/cloud-assembly-schema';
22
import type { HostedZone } from '@aws-sdk/client-route-53';
3+
import type { IContextProviderMessages } from '.';
34
import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api';
45
import type { IRoute53Client } from '../api';
56
import { type SdkProvider, initContextProviderSdk } from '../api/aws-auth/sdk-provider';
67
import type { ContextProviderPlugin } from '../api/plugin';
7-
import { debug } from '../logging';
88

99
export class HostedZoneContextProviderPlugin implements ContextProviderPlugin {
10-
constructor(private readonly aws: SdkProvider) {
10+
constructor(private readonly aws: SdkProvider, private readonly io: IContextProviderMessages) {
1111
}
1212

1313
public async getValue(args: HostedZoneContextQuery): Promise<object> {
@@ -17,7 +17,7 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin {
1717
throw new ContextProviderError(`HostedZoneProvider requires domainName property to be set in ${args}`);
1818
}
1919
const domainName = args.domainName;
20-
debug(`Reading hosted zone ${account}:${region}:${domainName}`);
20+
await this.io.debug(`Reading hosted zone ${account}:${region}:${domainName}`);
2121
const r53 = (await initContextProviderSdk(this.aws, args)).route53();
2222
const response = await r53.listHostedZonesByName({ DNSName: domainName });
2323
if (!response.HostedZones) {
@@ -42,9 +42,9 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin {
4242
): Promise<HostedZone[]> {
4343
let candidates: HostedZone[] = [];
4444
const domainName = props.domainName.endsWith('.') ? props.domainName : `${props.domainName}.`;
45-
debug(`Found the following zones ${JSON.stringify(zones)}`);
45+
await this.io.debug(`Found the following zones ${JSON.stringify(zones)}`);
4646
candidates = zones.filter((zone) => zone.Name === domainName);
47-
debug(`Found the following matched name zones ${JSON.stringify(candidates)}`);
47+
await this.io.debug(`Found the following matched name zones ${JSON.stringify(candidates)}`);
4848
if (props.privateZone) {
4949
candidates = candidates.filter((zone) => zone.Config && zone.Config.PrivateZone);
5050
} else {
@@ -55,7 +55,7 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin {
5555
for (const zone of candidates) {
5656
const data = await r53.getHostedZone({ Id: zone.Id });
5757
if (!data.VPCs) {
58-
debug(`Expected VPC for private zone but no VPC found ${zone.Id}`);
58+
await this.io.debug(`Expected VPC for private zone but no VPC found ${zone.Id}`);
5959
continue;
6060
}
6161
if (data.VPCs.map((vpc) => vpc.VPCId).includes(props.vpcId)) {

packages/aws-cdk/lib/context-providers/index.ts

+57-13
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,70 @@ import { SecurityGroupContextProviderPlugin } from './security-groups';
1111
import { SSMContextProviderPlugin } from './ssm-parameters';
1212
import { VpcNetworkContextProviderPlugin } from './vpcs';
1313
import { ContextProviderError } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api';
14+
import type { IoHelper } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
15+
import { IO } from '../../../@aws-cdk/tmp-toolkit-helpers/src/api/io/private';
1416
import type { SdkProvider } from '../api';
1517
import type { Context } from '../api/context';
1618
import { TRANSIENT_CONTEXT_KEY } from '../api/context';
1719
import { replaceEnvPlaceholders } from '../api/environment';
1820
import { PluginHost } from '../api/plugin';
1921
import type { ContextProviderPlugin } from '../api/plugin/context-provider-plugin';
20-
import { debug } from '../logging';
22+
import { CliIoHost } from '../cli/io-host';
2123
import { formatErrorMessage } from '../util';
2224

23-
export type ContextProviderFactory = ((sdk: SdkProvider) => ContextProviderPlugin);
24-
export type ProviderMap = {[name: string]: ContextProviderFactory};
25+
type ContextProviderFactory = ((sdk: SdkProvider, io: IContextProviderMessages) => ContextProviderPlugin);
26+
type ProviderMap = {[name: string]: ContextProviderFactory};
2527

2628
const PLUGIN_PROVIDER_PREFIX = 'plugin';
2729

30+
export interface IContextProviderMessages {
31+
/**
32+
* A message that is presented to users in normal mode of operation.
33+
*
34+
* Should be used sparingly. The Context Provider framework already provides useful output by default.
35+
* This can be uses in exceptionally situations, e.g. if a lookup call is expected to take a long time.
36+
*/
37+
info(message: string): Promise<void>;
38+
39+
/**
40+
* A message that helps users debugging the context provider.
41+
*
42+
* Should be used in most cases to note on current action.
43+
*/
44+
debug(message: string): Promise<void>;
45+
}
46+
47+
class ContextProviderMessages implements IContextProviderMessages {
48+
private readonly ioHelper: IoHelper;
49+
private readonly providerName: string;
50+
51+
public constructor(ioHelper: IoHelper, providerName: string) {
52+
this.ioHelper = ioHelper;
53+
this.providerName = providerName;
54+
}
55+
56+
public async info(message: string): Promise<void> {
57+
return this.ioHelper.notify(IO.CDK_ASSEMBLY_I0300.msg(message, {
58+
provider: this.providerName,
59+
}));
60+
}
61+
62+
public async debug(message: string): Promise<void> {
63+
return this.ioHelper.notify(IO.CDK_ASSEMBLY_I0301.msg(message, {
64+
provider: this.providerName,
65+
}));
66+
}
67+
}
68+
2869
/**
2970
* Iterate over the list of missing context values and invoke the appropriate providers from the map to retrieve them
3071
*/
3172
export async function provideContextValues(
3273
missingValues: cxschema.MissingContext[],
3374
context: Context,
34-
sdk: SdkProvider) {
75+
sdk: SdkProvider,
76+
ioHelper?: IoHelper,
77+
) {
3578
for (const missingContext of missingValues) {
3679
const key = missingContext.key;
3780

@@ -55,7 +98,8 @@ export async function provideContextValues(
5598
}
5699
}
57100

58-
const provider = factory(sdk);
101+
ioHelper = ioHelper ?? CliIoHost.instance().asIoHelper();
102+
const provider = factory(sdk, new ContextProviderMessages(ioHelper, providerName));
59103

60104
let value;
61105
try {
@@ -78,7 +122,7 @@ export async function provideContextValues(
78122
value = { [cxapi.PROVIDER_ERROR_KEY]: formatErrorMessage(e), [TRANSIENT_CONTEXT_KEY]: true };
79123
}
80124
context.set(key, value);
81-
debug(`Setting "${key}" context to ${JSON.stringify(value)}`);
125+
await ioHelper.notify(IO.DEFAULT_ASSEMBLY_DEBUG.msg(`Setting "${key}" context to ${JSON.stringify(value)}`));
82126
}
83127
}
84128

@@ -110,15 +154,15 @@ export function registerContextProviderFactory(name: string, provider: ContextPr
110154
}
111155

112156
const availableContextProviders: ProviderMap = {
113-
[cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER]: (s) => new AZContextProviderPlugin(s),
114-
[cxschema.ContextProvider.SSM_PARAMETER_PROVIDER]: (s) => new SSMContextProviderPlugin(s),
115-
[cxschema.ContextProvider.HOSTED_ZONE_PROVIDER]: (s) => new HostedZoneContextProviderPlugin(s),
116-
[cxschema.ContextProvider.VPC_PROVIDER]: (s) => new VpcNetworkContextProviderPlugin(s),
117-
[cxschema.ContextProvider.AMI_PROVIDER]: (s) => new AmiContextProviderPlugin(s),
118-
[cxschema.ContextProvider.ENDPOINT_SERVICE_AVAILABILITY_ZONE_PROVIDER]: (s) => new EndpointServiceAZContextProviderPlugin(s),
157+
[cxschema.ContextProvider.AVAILABILITY_ZONE_PROVIDER]: (s, io) => new AZContextProviderPlugin(s, io),
158+
[cxschema.ContextProvider.SSM_PARAMETER_PROVIDER]: (s, io) => new SSMContextProviderPlugin(s, io),
159+
[cxschema.ContextProvider.HOSTED_ZONE_PROVIDER]: (s, io) => new HostedZoneContextProviderPlugin(s, io),
160+
[cxschema.ContextProvider.VPC_PROVIDER]: (s, io) => new VpcNetworkContextProviderPlugin(s, io),
161+
[cxschema.ContextProvider.AMI_PROVIDER]: (s, io) => new AmiContextProviderPlugin(s, io),
162+
[cxschema.ContextProvider.ENDPOINT_SERVICE_AVAILABILITY_ZONE_PROVIDER]: (s, io) => new EndpointServiceAZContextProviderPlugin(s, io),
119163
[cxschema.ContextProvider.SECURITY_GROUP_PROVIDER]: (s) => new SecurityGroupContextProviderPlugin(s),
120164
[cxschema.ContextProvider.LOAD_BALANCER_PROVIDER]: (s) => new LoadBalancerContextProviderPlugin(s),
121165
[cxschema.ContextProvider.LOAD_BALANCER_LISTENER_PROVIDER]: (s) => new LoadBalancerListenerContextProviderPlugin(s),
122-
[cxschema.ContextProvider.KEY_PROVIDER]: (s) => new KeyContextProviderPlugin(s),
166+
[cxschema.ContextProvider.KEY_PROVIDER]: (s, io) => new KeyContextProviderPlugin(s, io),
123167
[cxschema.ContextProvider.CC_API_PROVIDER]: (s) => new CcApiContextProviderPlugin(s),
124168
};

0 commit comments

Comments
 (0)