Skip to content
Closed
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 @@ -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);
}
Expand Down Expand Up @@ -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,
}),
});
}
Expand Down
59 changes: 49 additions & 10 deletions packages/@aws-cdk/toolkit-lib/lib/api/diagnosing/stack-diagnoser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<SDK>;

/**
* Diagnose a stack's failed state
*
Expand All @@ -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();
Expand Down Expand Up @@ -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),
};
}

Expand Down Expand Up @@ -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))),
};
}
}
Expand All @@ -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<TracedResourceError[]> {
private async enhanceErrors(errs: readonly ResourceError[]): Promise<TracedResourceError[]> {
// 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<TracedResourceError> {
let sourceTrace;
private async enhanceError(err: ResourceError): Promise<TracedResourceError> {
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 };
}

Expand All @@ -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 ?? '',
Expand Down Expand Up @@ -355,6 +383,17 @@ export class CloudFormationStackDiagnoser {
}
return ret;
}

/**
* Return the additional exploration SDK, if available.
*/
private async additionalExplorationSdk(): Promise<SDK | undefined> {
if (!this.additionalExplorationSdkFetched) {
this.additionalExplorationSdkFetched = true;
this._additionalExplorationSdk = await this.props.additionalExplorationSdkProvider?.();
}
return this._additionalExplorationSdk;
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading