Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -430,6 +434,17 @@ export const IO = {
description: 'Indicates the use of a pre-synthesized cloud assembly directory',
}),

CDK_ASSEMBLY_I0300: make.info<ContextProviderMessageSource>({
code: 'CDK_ASSEMBLY_I0300',
description: 'An info message emitted by a Context Provider',
interface: 'ContextProviderMessageSource',
}),
CDK_ASSEMBLY_I0301: make.debug<ContextProviderMessageSource>({
code: 'CDK_ASSEMBLY_I0301',
description: 'A debug message emitted by a Context Provider',
interface: 'ContextProviderMessageSource',
}),

// Assembly Annotations
CDK_ASSEMBLY_I9999: make.info<cxapi.SynthesisMessage>({
code: 'CDK_ASSEMBLY_I9999',
Expand Down
3 changes: 3 additions & 0 deletions packages/@aws-cdk/toolkit-lib/docs/message-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -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} |
Expand All @@ -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) |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion packages/aws-cdk/lib/api/cxapp/cloud-executable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
8 changes: 6 additions & 2 deletions packages/aws-cdk/lib/cli/io-host/cli-io-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}

/**
Expand Down
10 changes: 5 additions & 5 deletions packages/aws-cdk/lib/context-providers/ami.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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({
Expand All @@ -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!;
}
}
Expand Down
6 changes: 3 additions & 3 deletions packages/aws-cdk/lib/context-providers/availability-zones.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
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],
});

// 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;
}
}
12 changes: 6 additions & 6 deletions packages/aws-cdk/lib/context-providers/hosted-zones.ts
Original file line number Diff line number Diff line change
@@ -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<object> {
Expand All @@ -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) {
Expand All @@ -42,9 +42,9 @@ export class HostedZoneContextProviderPlugin implements ContextProviderPlugin {
): Promise<HostedZone[]> {
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 {
Expand All @@ -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)) {
Expand Down
70 changes: 57 additions & 13 deletions packages/aws-cdk/lib/context-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;

/**
* A message that helps users debugging the context provider.
*
* Should be used in most cases to note on current action.
*/
debug(message: string): Promise<void>;
}

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<void> {
return this.ioHelper.notify(IO.CDK_ASSEMBLY_I0300.msg(message, {
provider: this.providerName,
}));
}

public async debug(message: string): Promise<void> {
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;

Expand All @@ -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 {
Expand All @@ -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)}`));
}
}

Expand Down Expand Up @@ -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),
};
Loading