Skip to content

Commit b1d8a8f

Browse files
authored
Merge branch 'main' into conroy/bootstrapupdate
2 parents 7f41156 + 3818234 commit b1d8a8f

14 files changed

Lines changed: 190 additions & 93 deletions

File tree

packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ export type AwsContext = { readonly aws: AwsClients };
1010
*
1111
* Allocate the next region from the REGION pool and dispose it afterwards.
1212
*/
13-
export function withAws(
14-
block: (context: TestContext & AwsContext & DisableBootstrapContext) => Promise<void>,
13+
export function withAws<A extends TestContext>(
14+
block: (context: A & AwsContext & DisableBootstrapContext) => Promise<void>,
1515
disableBootstrap: boolean = false,
16-
): (context: TestContext) => Promise<void> {
17-
return (context: TestContext) => regionPool().using(async (region) => {
16+
): (context: A) => Promise<void> {
17+
return (context: A) => regionPool().using(async (region) => {
1818
const aws = await AwsClients.forRegion(region, context.output);
1919
await sanityCheck(aws);
2020

packages/@aws-cdk-testing/cli-integ/tests/tool-integrations/amplify.integtest.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { promises as fs } from 'fs';
22
import * as path from 'path';
3-
import { integTest, withTemporaryDirectory, ShellHelper, withPackages, TemporaryDirectoryContext } from '../../lib';
3+
import { withToolContext } from './with-tool-context';
4+
import { integTest, ShellHelper, TemporaryDirectoryContext } from '../../lib';
45

56
const TIMEOUT = 1800_000;
67

7-
integTest('amplify integration', withTemporaryDirectory(withPackages(async (context) => {
8+
integTest('amplify integration', withToolContext(async (context) => {
89
const shell = ShellHelper.fromContext(context);
910

1011
await shell.shell(['npm', 'create', '-y', 'amplify@latest']);
@@ -14,9 +15,24 @@ integTest('amplify integration', withTemporaryDirectory(withPackages(async (cont
1415
await updateCdkDependency(context, context.packages.requestedCliVersion(), context.packages.requestedFrameworkVersion());
1516
await shell.shell(['npm', 'install']);
1617

17-
await shell.shell(['npx', 'ampx', 'sandbox', '--once']);
18-
await shell.shell(['npx', 'ampx', 'sandbox', 'delete', '--yes']);
19-
})), TIMEOUT);
18+
await shell.shell(['npx', 'ampx', 'sandbox', '--once'], {
19+
modEnv: {
20+
AWS_REGION: context.aws.region,
21+
},
22+
});
23+
try {
24+
25+
// Future code goes here, putting the try/finally here already so it doesn't
26+
// get forgotten.
27+
28+
} finally {
29+
await shell.shell(['npx', 'ampx', 'sandbox', 'delete', '--yes'], {
30+
modEnv: {
31+
AWS_REGION: context.aws.region,
32+
},
33+
});
34+
}
35+
}), TIMEOUT);
2036

2137
async function updateCdkDependency(context: TemporaryDirectoryContext, cliVersion: string, libVersion: string) {
2238
const filename = path.join(context.integTestDir, 'package.json');
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { TestContext } from '../../lib/integ-test';
2+
import { AwsContext, withAws } from '../../lib/with-aws';
3+
import { DisableBootstrapContext } from '../../lib/with-cdk-app';
4+
import { PackageContext, withPackages } from '../../lib/with-packages';
5+
import { TemporaryDirectoryContext, withTemporaryDirectory } from '../../lib/with-temporary-directory';
6+
7+
/**
8+
* The default prerequisites for tests running tool integrations
9+
*/
10+
export function withToolContext<A extends TestContext>(
11+
block: (context: A & TemporaryDirectoryContext & PackageContext & AwsContext & DisableBootstrapContext
12+
) => Promise<void>) {
13+
return withAws(withTemporaryDirectory(withPackages(block)));
14+
}

packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
230230
*/
231231
public abstract readonly ownerAccountId: string;
232232

233+
/**
234+
* IPv4 CIDR provisioned under pool
235+
* Required to check for overlapping CIDRs after provisioning
236+
* is complete under IPAM pool
237+
*/
238+
public abstract readonly ipv4IpamProvisionedCidrs?: string[];
239+
233240
/**
234241
* If this is set to true, don't error out on trying to select subnets
235242
*/

packages/@aws-cdk/aws-location-alpha/lib/place-index.ts

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -74,39 +74,12 @@ export enum IntendedUse {
7474
STORAGE = 'Storage',
7575
}
7676

77-
abstract class PlaceIndexBase extends Resource implements IPlaceIndex {
78-
public abstract readonly placeIndexName: string;
79-
public abstract readonly placeIndexArn: string;
80-
81-
/**
82-
* Grant the given principal identity permissions to perform the actions on this place index.
83-
*/
84-
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
85-
return iam.Grant.addToPrincipal({
86-
grantee: grantee,
87-
actions: actions,
88-
resourceArns: [this.placeIndexArn],
89-
});
90-
}
91-
92-
/**
93-
* Grant the given identity permissions to search using this index
94-
*/
95-
public grantSearch(grantee: iam.IGrantable): iam.Grant {
96-
return this.grant(grantee,
97-
'geo:SearchPlaceIndexForPosition',
98-
'geo:SearchPlaceIndexForSuggestions',
99-
'geo:SearchPlaceIndexForText',
100-
);
101-
}
102-
}
103-
10477
/**
10578
* A Place Index
10679
*
10780
* @see https://docs.aws.amazon.com/location/latest/developerguide/places-concepts.html
10881
*/
109-
export class PlaceIndex extends PlaceIndexBase {
82+
export class PlaceIndex extends Resource implements IPlaceIndex {
11083
/**
11184
* Use an existing place index by name
11285
*/
@@ -130,7 +103,7 @@ export class PlaceIndex extends PlaceIndexBase {
130103
throw new Error(`Place Index Arn ${placeIndexArn} does not have a resource name.`);
131104
}
132105

133-
class Import extends PlaceIndexBase {
106+
class Import extends Resource implements IPlaceIndex {
134107
public readonly placeIndexName = parsedArn.resourceName!;
135108
public readonly placeIndexArn = placeIndexArn;
136109
}
@@ -187,4 +160,25 @@ export class PlaceIndex extends PlaceIndexBase {
187160
this.placeIndexUpdateTime = placeIndex.attrUpdateTime;
188161
}
189162

163+
/**
164+
* Grant the given principal identity permissions to perform the actions on this place index.
165+
*/
166+
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
167+
return iam.Grant.addToPrincipal({
168+
grantee: grantee,
169+
actions: actions,
170+
resourceArns: [this.placeIndexArn],
171+
});
172+
}
173+
174+
/**
175+
* Grant the given identity permissions to search using this index
176+
*/
177+
public grantSearch(grantee: iam.IGrantable): iam.Grant {
178+
return this.grant(grantee,
179+
'geo:SearchPlaceIndexForPosition',
180+
'geo:SearchPlaceIndexForSuggestions',
181+
'geo:SearchPlaceIndexForText',
182+
);
183+
}
190184
}

packages/@aws-cdk/aws-scheduler-alpha/README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,6 @@ const cronBasedSchedule = new Schedule(this, 'Schedule', {
8585
A one-time schedule is a schedule that invokes a target only once. You configure a one-time schedule when by specifying the time of the day, date,
8686
and time zone in which EventBridge Scheduler evaluates the schedule.
8787

88-
[comment]: <> (TODO: Switch to `ts` once Schedule is implemented)
89-
9088
```ts
9189
declare const target: targets.LambdaInvoke;
9290

@@ -208,11 +206,6 @@ const target = new targets.LambdaInvoke(fn, {
208206
});
209207
```
210208

211-
212-
### Cross-account and cross-region targets
213-
214-
Executing cross-account and cross-region targets are not supported yet.
215-
216209
### Specifying Encryption key
217210

218211
EventBridge Scheduler integrates with AWS Key Management Service (AWS KMS) to encrypt and decrypt your data using an AWS KMS key.

packages/@aws-cdk/aws-scheduler-alpha/lib/input.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@ import { DefaultTokenResolver, IResolveContext, Stack, StringConcat, Token, Toke
22
import { ISchedule } from './schedule';
33

44
/**
5-
* The text, or well-formed JSON, passed to the target of the schedule.
5+
* The text or well-formed JSON input passed to the target of the schedule.
6+
* Tokens and ContextAttribute may be used in the input.
67
*/
78
export abstract class ScheduleTargetInput {
89
/**
9-
* Pass text to the target, it is possible to embed `ContextAttributes`
10-
* that will be resolved to actual values while the CloudFormation is
11-
* deployed or cdk Tokens that will be resolved when the CloudFormation
12-
* templates are generated by CDK.
13-
*
14-
* The target input value will be a single string that you pass.
15-
* For passing complex values like JSON object to a target use method
10+
* Pass simple text to the target. For passing complex values like JSON object to a target use method
1611
* `ScheduleTargetInput.fromObject()` instead.
1712
*
1813
* @param text Text to use as the input for the target
@@ -22,8 +17,7 @@ export abstract class ScheduleTargetInput {
2217
}
2318

2419
/**
25-
* Pass a JSON object to the target, it is possible to embed `ContextAttributes` and other
26-
* cdk references.
20+
* Pass a JSON object to the target. The object will be transformed into a well-formed JSON string in the final template.
2721
*
2822
* @param obj object to use to convert to JSON to use as input for the target
2923
*/
@@ -66,7 +60,8 @@ class FieldAwareEventInput extends ScheduleTargetInput {
6660
}
6761

6862
/**
69-
* Represents a field in the event pattern
63+
* A set of convenient static methods representing the Scheduler Context Attributes.
64+
* These Context Attributes keywords can be used inside a ScheduleTargetInput.
7065
*
7166
* @see https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-schedule-context-attributes.html
7267
*/
@@ -103,7 +98,7 @@ export class ContextAttribute {
10398
}
10499

105100
/**
106-
* Escape hatch for other ContextAttribute that might be resolved in future.
101+
* Escape hatch for other Context Attributes that may be added in the future
107102
*
108103
* @param name - name will replace xxx in <aws.scheduler.xxx>
109104
*/

packages/aws-cdk-lib/core/lib/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ export class App extends Stage {
192192
if (autoSynth) {
193193
// synth() guarantees it will only execute once, so a default of 'true'
194194
// doesn't bite manual calling of the function.
195-
process.once('beforeExit', () => this.synth());
195+
process.once('beforeExit', () => this.synth({ errorOnDuplicateSynth: false }));
196196
}
197197

198198
this._treeMetadata = props.treeMetadata ?? true;

packages/aws-cdk-lib/core/lib/stage.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ export class Stage extends Construct {
146146
*/
147147
private assembly?: cxapi.CloudAssembly;
148148

149+
/**
150+
* The cached set of construct paths. Empty if assembly was not yet built.
151+
*/
152+
private constructPathsCache: Set<string>;
153+
149154
/**
150155
* Validation plugins to run during synthesis. If any plugin reports any violation,
151156
* synthesis will be interrupted and the report displayed to the user.
@@ -163,6 +168,7 @@ export class Stage extends Construct {
163168

164169
Object.defineProperty(this, STAGE_SYMBOL, { value: true });
165170

171+
this.constructPathsCache = new Set<string>();
166172
this.parentStage = Stage.of(this);
167173

168174
this.region = props.env?.region ?? this.parentStage?.region;
@@ -210,16 +216,62 @@ export class Stage extends Construct {
210216
* calls will return the same assembly.
211217
*/
212218
public synth(options: StageSynthesisOptions = { }): cxapi.CloudAssembly {
213-
if (!this.assembly || options.force) {
219+
220+
let newConstructPaths = this.listAllConstructPaths(this);
221+
222+
// If the assembly cache is uninitiazed, run synthesize and reset construct paths cache
223+
if (this.constructPathsCache.size == 0 || !this.assembly || options.force) {
214224
this.assembly = synthesize(this, {
215225
skipValidation: options.skipValidation,
216226
validateOnSynthesis: options.validateOnSynthesis,
217227
});
228+
newConstructPaths = this.listAllConstructPaths(this);
229+
this.constructPathsCache = newConstructPaths;
218230
}
219231

232+
// If the construct paths set has changed
233+
if (!this.constructPathSetsAreEqual(this.constructPathsCache, newConstructPaths)) {
234+
const errorMessage = 'Synthesis has been called multiple times and the construct tree was modified after the first synthesis.';
235+
if (options.errorOnDuplicateSynth ?? true) {
236+
throw new Error(errorMessage + ' This is not allowed. Remove multple synth() calls and do not modify the construct tree after the first synth().');
237+
} else {
238+
// eslint-disable-next-line no-console
239+
console.error(errorMessage + ' Only the results of the first synth() call are used, and modifications done after it are ignored. Avoid construct tree mutations after synth() has been called unless this is intentional.');
240+
}
241+
}
242+
243+
// Reset construct paths cache
244+
this.constructPathsCache = newConstructPaths;
245+
220246
return this.assembly;
221247
}
222248

249+
// Function that lists all construct paths and returns them as a set
250+
private listAllConstructPaths(construct: IConstruct): Set<string> {
251+
const paths = new Set<string>();
252+
function recurse(root: IConstruct) {
253+
paths.add(root.node.path);
254+
for (const child of root.node.children) {
255+
if (!Stage.isStage(child)) {
256+
recurse(child);
257+
}
258+
}
259+
}
260+
recurse(construct);
261+
return paths;
262+
}
263+
264+
// Checks if sets of construct paths are equal
265+
private constructPathSetsAreEqual(set1: Set<string>, set2: Set<string>): boolean {
266+
if (set1.size !== set2.size) return false;
267+
for (const id of set1) {
268+
if (!set2.has(id)) {
269+
return false;
270+
}
271+
}
272+
return true;
273+
}
274+
223275
private createBuilder(outdir?: string) {
224276
// cannot specify "outdir" if we are a nested stage
225277
if (this.parentStage && outdir) {
@@ -259,4 +311,11 @@ export interface StageSynthesisOptions {
259311
* @default false
260312
*/
261313
readonly force?: boolean;
314+
315+
/**
316+
* Whether or not to throw a warning instead of an error if the construct tree has
317+
* been mutated since the last synth.
318+
* @default true
319+
*/
320+
readonly errorOnDuplicateSynth?: boolean;
262321
}

packages/aws-cdk-lib/core/test/synthesis.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as os from 'os';
33
import * as path from 'path';
44
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
55
import { Construct } from 'constructs';
6+
import { Template } from '../../assertions';
67
import * as cxschema from '../../cloud-assembly-schema';
78
import * as cxapi from '../../cx-api';
89
import * as cdk from '../lib';
@@ -362,6 +363,30 @@ describe('synthesis', () => {
362363

363364
});
364365

366+
test('calling synth multiple times errors if construct tree is mutated', () => {
367+
const app = new cdk.App();
368+
369+
const stages = [
370+
{
371+
stage: 'PROD',
372+
},
373+
{
374+
stage: 'BETA',
375+
},
376+
];
377+
378+
// THEN - no error the first time synth is called
379+
let stack = new cdk.Stack(app, `${stages[0].stage}-Stack`, {});
380+
expect(() => {
381+
Template.fromStack(stack);
382+
}).not.toThrow();
383+
384+
// THEN - error is thrown since synth was called with mutated stack name
385+
stack = new cdk.Stack(app, `${stages[1].stage}-Stack`, {});
386+
expect(() => {
387+
Template.fromStack(stack);
388+
}).toThrow('Synthesis has been called multiple times and the construct tree was modified after the first synthesis');
389+
});
365390
});
366391

367392
function list(outdir: string) {

0 commit comments

Comments
 (0)