diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts index 85793fd8c..ddc26d358 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/deployments/deployments.ts @@ -416,6 +416,7 @@ export class Deployments { sourceTracer: new StackArtifactSourceTracer(options.stack), ioHelper: this.ioHelper, topLevelStackHierarchicalId: options.stack.hierarchicalId, + additionalExplorationSdkProvider: async () => (await this.envs.accessStackForLookupBestEffort(options.stack)).sdk, }), }, this.ioHelper); } @@ -486,6 +487,7 @@ export class Deployments { sourceTracer: new StackArtifactSourceTracer(stack), ioHelper: this.ioHelper, topLevelStackHierarchicalId: stack.hierarchicalId, + additionalExplorationSdkProvider: async () => (await this.envs.accessStackForLookupBestEffort(stack)).sdk, }), }); } diff --git a/packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts b/packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts index cb8330d72..e3efaff26 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts @@ -17,8 +17,24 @@ export interface CloudFormationStackDiagnoserProps { readonly sourceTracer: ISourceTracer; readonly ioHelper: IoHelper; readonly topLevelStackHierarchicalId: string; + + /** + * Optionally: a function to return an SDK that can be used for additional + * (readonly) exploratory calls. + * + * Typically, this should be an SDK that is primed with the "lookup" role, or similar. + * + * This is necessary because the "deploy" role will not typically have permissions to + * do very much. + * + * Regardless, if lookups of additional information fail, they are emitted at debug + * level and their information is simply not added to the output. + */ + readonly additionalExplorationSdkProvider?: SdkProvider; } +export type SdkProvider = () => Promise; + /** * Diagnose a stack's failed state * @@ -35,6 +51,8 @@ export interface CloudFormationStackDiagnoserProps { export class CloudFormationStackDiagnoser { private readonly cfn: ICloudFormationClient; private parentStackLogicalIds: string[]; + private additionalExplorationSdkFetched = false; + private _additionalExplorationSdk?: SDK; constructor(private readonly props: CloudFormationStackDiagnoserProps) { this.cfn = this.props.sdk.cloudFormation(); @@ -99,7 +117,7 @@ export class CloudFormationStackDiagnoser { stackStatus: stack.StackStatus ?? '', statusReason: stack.StackStatusReason ?? '', }, - problems: await this.addErrorTraces(errors.all), + problems: await this.enhanceErrors(errors.all), }; } @@ -183,7 +201,7 @@ export class CloudFormationStackDiagnoser { type: 'early-validation', changeSetName: changeSet.ChangeSetName ?? '', }, - problems: await this.addErrorTraces(ev.errors.map((e) => resourceErrorFromEarlyValidationError(changeSet.StackId ?? '', this.parentStackLogicalIds, e))), + problems: await this.enhanceErrors(ev.errors.map((e) => resourceErrorFromEarlyValidationError(changeSet.StackId ?? '', this.parentStackLogicalIds, e))), }; } } @@ -202,26 +220,36 @@ export class CloudFormationStackDiagnoser { changeSetName: changeSet.ChangeSetName ?? '', statusReason: changeSet.StatusReason ?? '', }, - problems: await this.addErrorTraces(failedAutoErrors), + problems: await this.enhanceErrors(failedAutoErrors), }; } return this._nonSpecificChangeSetError(changeSet); } - private async addErrorTraces(errs: readonly ResourceError[]): Promise { + private async enhanceErrors(errs: readonly ResourceError[]): Promise { + // We're not actually limiting this here. But we are making the assumption that the amount of resources + // that will have errors are always pretty low in number. // eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism - return Promise.all(errs.map((e) => this.addErrorTrace(e))); + return Promise.all(errs.map((e) => this.enhanceError(e))); } - private async addErrorTrace(err: ResourceError): Promise { - let sourceTrace; + private async enhanceError(err: ResourceError): Promise { + let sourceTracePromise; if (err.logicalId) { - sourceTrace = await this.props.sourceTracer.traceResource(err.stackArn, err.parentStackLogicalIds, err.logicalId); + sourceTracePromise = await this.props.sourceTracer.traceResource(err.stackArn, err.parentStackLogicalIds, err.logicalId); } else { - sourceTrace = await this.props.sourceTracer.traceStack(err.stackArn, err.parentStackLogicalIds); + sourceTracePromise = await this.props.sourceTracer.traceStack(err.stackArn, err.parentStackLogicalIds); } + // eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism + const [sourceTrace] = await Promise.all([ + sourceTracePromise, + ]); + + const addl = await this.additionalExplorationSdk(); + void addl; + return { ...err, sourceTrace, topLevelStackHierarchicalId: this.props.topLevelStackHierarchicalId }; } @@ -240,7 +268,7 @@ export class CloudFormationStackDiagnoser { statusReason: changeSet.StatusReason ?? '', }, problems: [ - await this.addErrorTrace({ + await this.enhanceError({ // It's about a stack logicalId: undefined, message: changeSet.StatusReason ?? '', @@ -355,6 +383,17 @@ export class CloudFormationStackDiagnoser { } return ret; } + + /** + * Return the additional exploration SDK, if available. + */ + private async additionalExplorationSdk(): Promise { + if (!this.additionalExplorationSdkFetched) { + this.additionalExplorationSdkFetched = true; + this._additionalExplorationSdk = await this.props.additionalExplorationSdkProvider?.(); + } + return this._additionalExplorationSdk; + } } /** diff --git a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts index c8f6fd754..cfab4f1c7 100644 --- a/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts +++ b/packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts @@ -629,6 +629,7 @@ export class Toolkit extends CloudAssemblySourceBuilder { sourceTracer: new StackArtifactSourceTracer(stack), ioHelper, topLevelStackHierarchicalId: stack.hierarchicalId, + additionalExplorationSdkProvider: () => Promise.resolve(stackEnv.sdk), }); const diagnosis = await diagnoser.diagnoseFromFresh(stack.stackName);