Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli): excessive stack event polling during deployment #32196

Merged
merged 11 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
44 changes: 21 additions & 23 deletions packages/aws-cdk/lib/api/aws-auth/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
type DescribeResourceScanCommandInput,
type DescribeResourceScanCommandOutput,
type DescribeStackEventsCommandInput,
DescribeStackEventsCommandOutput,
DescribeStackResourcesCommand,
DescribeStackResourcesCommandInput,
DescribeStackResourcesCommandOutput,
Expand Down Expand Up @@ -91,7 +92,6 @@ import {
RollbackStackCommand,
RollbackStackCommandInput,
RollbackStackCommandOutput,
StackEvent,
StackResourceSummary,
StartResourceScanCommand,
type StartResourceScanCommandInput,
Expand Down Expand Up @@ -311,7 +311,7 @@ import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
import { Upload } from '@aws-sdk/lib-storage';
import { getEndpointFromInstructions } from '@smithy/middleware-endpoint';
import type { NodeHttpHandlerOptions } from '@smithy/node-http-handler';
import { AwsCredentialIdentity, Logger } from '@smithy/types';
import { AwsCredentialIdentity, Logger, Paginator } from '@smithy/types';
import { ConfiguredRetryStrategy } from '@smithy/util-retry';
import { WaiterResult } from '@smithy/util-waiter';
import { AccountAccessKeyCache } from './account-cache';
Expand Down Expand Up @@ -365,8 +365,7 @@ export interface IAppSyncClient {
updateApiKey(input: UpdateApiKeyCommandInput): Promise<UpdateApiKeyCommandOutput>;
updateFunction(input: UpdateFunctionCommandInput): Promise<UpdateFunctionCommandOutput>;
updateResolver(input: UpdateResolverCommandInput): Promise<UpdateResolverCommandOutput>;
// Pagination functions
listFunctions(input: ListFunctionsCommandInput): Promise<FunctionConfiguration[]>;
listAllFunctions(input: ListFunctionsCommandInput): Promise<FunctionConfiguration[]>;
}

export interface ICloudFormationClient {
Expand Down Expand Up @@ -403,9 +402,8 @@ export interface ICloudFormationClient {
updateTerminationProtection(
input: UpdateTerminationProtectionCommandInput,
): Promise<UpdateTerminationProtectionCommandOutput>;
// Pagination functions
describeStackEvents(input: DescribeStackEventsCommandInput): Promise<StackEvent[]>;
listStackResources(input: ListStackResourcesCommandInput): Promise<StackResourceSummary[]>;
describeStackEventsPaginated(input: DescribeStackEventsCommandInput): Paginator<DescribeStackEventsCommandOutput>;
listAllStackResources(input: ListStackResourcesCommandInput): Promise<StackResourceSummary[]>;
}

export interface ICloudWatchLogsClient {
Expand Down Expand Up @@ -455,12 +453,19 @@ export interface IECSClient {
}

export interface IElasticLoadBalancingV2Client {
/**
* Returns only the first page. Use `.NextMarker` on the result to query for the next page.
* To retrieve all results, use `describeAllListeners`.
*/
describeListeners(input: DescribeListenersCommandInput): Promise<DescribeListenersCommandOutput>;
/**
* Returns only the first page. Use `.NextMarker` on the result to query for the next page.
* To retrieve all results, use `describeAllLoadBalancers`.
*/
describeLoadBalancers(input: DescribeLoadBalancersCommandInput): Promise<DescribeLoadBalancersCommandOutput>;
describeTags(input: DescribeTagsCommandInput): Promise<DescribeTagsCommandOutput>;
// Pagination
paginateDescribeListeners(input: DescribeListenersCommandInput): Promise<Listener[]>;
paginateDescribeLoadBalancers(input: DescribeLoadBalancersCommandInput): Promise<LoadBalancer[]>;
describeAllListeners(input: DescribeListenersCommandInput): Promise<Listener[]>;
describeAllLoadBalancers(input: DescribeLoadBalancersCommandInput): Promise<LoadBalancer[]>;
}

export interface IIAMClient {
Expand Down Expand Up @@ -587,8 +592,7 @@ export class SDK {
updateResolver: (input: UpdateResolverCommandInput): Promise<UpdateResolverCommandOutput> =>
client.send(new UpdateResolverCommand(input)),

// Pagination Functions
listFunctions: async (input: ListFunctionsCommandInput): Promise<FunctionConfiguration[]> => {
listAllFunctions: async (input: ListFunctionsCommandInput): Promise<FunctionConfiguration[]> => {
const functions = Array<FunctionConfiguration>();
const paginator = paginateListFunctions({ client }, input);
for await (const page of paginator) {
Expand Down Expand Up @@ -664,15 +668,10 @@ export class SDK {
input: UpdateTerminationProtectionCommandInput,
): Promise<UpdateTerminationProtectionCommandOutput> =>
client.send(new UpdateTerminationProtectionCommand(input)),
describeStackEvents: async (input: DescribeStackEventsCommandInput): Promise<StackEvent[]> => {
const stackEvents = Array<StackEvent>();
const paginator = paginateDescribeStackEvents({ client }, input);
for await (const page of paginator) {
stackEvents.push(...(page?.StackEvents || []));
}
return stackEvents;
describeStackEventsPaginated: (input: DescribeStackEventsCommandInput): Paginator<DescribeStackEventsCommandOutput> => {
return paginateDescribeStackEvents({ client }, input);
},
listStackResources: async (input: ListStackResourcesCommandInput): Promise<StackResourceSummary[]> => {
listAllStackResources: async (input: ListStackResourcesCommandInput): Promise<StackResourceSummary[]> => {
const stackResources = Array<StackResourceSummary>();
const paginator = paginateListStackResources({ client }, input);
for await (const page of paginator) {
Expand Down Expand Up @@ -789,16 +788,15 @@ export class SDK {
client.send(new DescribeLoadBalancersCommand(input)),
describeTags: (input: DescribeTagsCommandInput): Promise<DescribeTagsCommandOutput> =>
client.send(new DescribeTagsCommand(input)),
// Pagination Functions
paginateDescribeListeners: async (input: DescribeListenersCommandInput): Promise<Listener[]> => {
describeAllListeners: async (input: DescribeListenersCommandInput): Promise<Listener[]> => {
const listeners = Array<Listener>();
const paginator = paginateDescribeListeners({ client }, input);
for await (const page of paginator) {
listeners.push(...(page?.Listeners || []));
}
return listeners;
},
paginateDescribeLoadBalancers: async (input: DescribeLoadBalancersCommandInput): Promise<LoadBalancer[]> => {
describeAllLoadBalancers: async (input: DescribeLoadBalancersCommandInput): Promise<LoadBalancer[]> => {
const loadBalancers = Array<LoadBalancer>();
const paginator = paginateDescribeLoadBalancers({ client }, input);
for await (const page of paginator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class LazyListStackResources implements ListStackResources {

public async listStackResources(): Promise<StackResourceSummary[]> {
if (this.stackResources === undefined) {
this.stackResources = this.sdk.cloudFormation().listStackResources({
this.stackResources = this.sdk.cloudFormation().listAllStackResources({
StackName: this.stackName,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export async function isHotswappableAppSyncChange(
delete sdkRequestObject.runtime;
}

const functions = await sdk.appsync().listFunctions({ apiId: sdkRequestObject.apiId });
const functions = await sdk.appsync().listAllFunctions({ apiId: sdkRequestObject.apiId });
const { functionId } = functions.find((fn) => fn.name === physicalName) ?? {};
// Updating multiple functions at the same time or along with graphql schema results in `ConcurrentModificationException`
await simpleRetry(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,67 +86,61 @@ export class StackEventPoller {
}

private async doPoll(): Promise<ResourceEvent[]> {
// eslint-disable-next-line no-console
console.error('Polling for events');
const events: ResourceEvent[] = [];
try {
const eventList = await this.cfn.describeStackEvents({
const paginator = this.cfn.describeStackEventsPaginated({
StackName: this.props.stackName,
});
for (const event of eventList) {
// Event from before we were interested in 'em
if (this.props.startTime !== undefined && event.Timestamp!.valueOf() < this.props.startTime) {
return events;
}

// Already seen this one
if (this.eventIds.has(event.EventId!)) {
return events;
}
this.eventIds.add(event.EventId!);

// The events for the stack itself are also included next to events about resources; we can test for them in this way.
const isParentStackEvent = event.PhysicalResourceId === event.StackId;

if (isParentStackEvent && this.props.stackStatuses?.includes(event.ResourceStatus ?? '')) {
return events;
}

// Fresh event
const resEvent: ResourceEvent = {
event: event,
parentStackLogicalIds: this.props.parentStackLogicalIds ?? [],
isStackEvent: isParentStackEvent,
};
events.push(resEvent);

if (
!isParentStackEvent &&
event.ResourceType === 'AWS::CloudFormation::Stack' &&
isStackBeginOperationState(event.ResourceStatus)
) {
// If the event is not for `this` stack and has a physical resource Id, recursively call for events in the nested stack
this.trackNestedStack(event, [...(this.props.parentStackLogicalIds ?? []), event.LogicalResourceId ?? '']);
}

if (isParentStackEvent && isStackTerminalState(event.ResourceStatus)) {
this.complete = true;
for await (const page of paginator) {
for (const event of page.StackEvents ?? []) {
// Event from before we were interested in 'em
if (this.props.startTime !== undefined && event.Timestamp!.valueOf() < this.props.startTime) {
return events;
}

// Already seen this one
if (this.eventIds.has(event.EventId!)) {
return events;
}
this.eventIds.add(event.EventId!);

// The events for the stack itself are also included next to events about resources; we can test for them in this way.
const isParentStackEvent = event.PhysicalResourceId === event.StackId;

if (isParentStackEvent && this.props.stackStatuses?.includes(event.ResourceStatus ?? '')) {
return events;
}

// Fresh event
const resEvent: ResourceEvent = {
event: event,
parentStackLogicalIds: this.props.parentStackLogicalIds ?? [],
isStackEvent: isParentStackEvent,
};
events.push(resEvent);

if (
!isParentStackEvent &&
event.ResourceType === 'AWS::CloudFormation::Stack' &&
isStackBeginOperationState(event.ResourceStatus)
) {
// If the event is not for `this` stack and has a physical resource Id, recursively call for events in the nested stack
this.trackNestedStack(event, [...(this.props.parentStackLogicalIds ?? []), event.LogicalResourceId ?? '']);
}

if (isParentStackEvent && isStackTerminalState(event.ResourceStatus)) {
this.complete = true;
}
}
}
} catch (e: any) {
if (!(e.name === 'ValidationError' && e.message === `Stack [${this.props.stackName}] does not exist`)) {
throw e;
}
}
// // Also poll all nested stacks we're currently tracking
// for (const [logicalId, poller] of Object.entries(this.nestedStackPollers)) {
// events.push(...(await poller.poll()));
// if (poller.complete) {
// delete this.nestedStackPollers[logicalId];
// }
// }

// // Return what we have so far
// events.sort((a, b) => a.event.Timestamp!.valueOf() - b.event.Timestamp!.valueOf());
// this.events.push(...events);

return events;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/aws-cdk/lib/context-providers/load-balancers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class LoadBalancerProvider {
private async getLoadBalancers() {
const loadBalancerArns = this.filter.loadBalancerArn ? [this.filter.loadBalancerArn] : undefined;
const loadBalancers = (
await this.client.paginateDescribeLoadBalancers({
await this.client.describeAllLoadBalancers({
LoadBalancerArns: loadBalancerArns,
})
).filter((lb) => lb.Type === this.filter.loadBalancerType);
Expand Down Expand Up @@ -193,7 +193,7 @@ class LoadBalancerProvider {
private async getListenersForLoadBalancers(loadBalancers: LoadBalancer[]): Promise<Listener[]> {
const listeners: Listener[] = [];
for (const loadBalancer of loadBalancers.map((lb) => lb.LoadBalancerArn)) {
listeners.push(...(await this.client.paginateDescribeListeners({ LoadBalancerArn: loadBalancer })));
listeners.push(...(await this.client.describeAllListeners({ LoadBalancerArn: loadBalancer })));
}
return listeners;
}
Expand Down
Loading