Skip to content

Commit

Permalink
Merge branch 'main' into conroy/bootstrapupdate
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Oct 30, 2024
2 parents 7f41156 + 3818234 commit b1d8a8f
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 93 deletions.
8 changes: 4 additions & 4 deletions packages/@aws-cdk-testing/cli-integ/lib/with-aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ export type AwsContext = { readonly aws: AwsClients };
*
* Allocate the next region from the REGION pool and dispose it afterwards.
*/
export function withAws(
block: (context: TestContext & AwsContext & DisableBootstrapContext) => Promise<void>,
export function withAws<A extends TestContext>(
block: (context: A & AwsContext & DisableBootstrapContext) => Promise<void>,
disableBootstrap: boolean = false,
): (context: TestContext) => Promise<void> {
return (context: TestContext) => regionPool().using(async (region) => {
): (context: A) => Promise<void> {
return (context: A) => regionPool().using(async (region) => {
const aws = await AwsClients.forRegion(region, context.output);
await sanityCheck(aws);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { promises as fs } from 'fs';
import * as path from 'path';
import { integTest, withTemporaryDirectory, ShellHelper, withPackages, TemporaryDirectoryContext } from '../../lib';
import { withToolContext } from './with-tool-context';
import { integTest, ShellHelper, TemporaryDirectoryContext } from '../../lib';

const TIMEOUT = 1800_000;

integTest('amplify integration', withTemporaryDirectory(withPackages(async (context) => {
integTest('amplify integration', withToolContext(async (context) => {
const shell = ShellHelper.fromContext(context);

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

await shell.shell(['npx', 'ampx', 'sandbox', '--once']);
await shell.shell(['npx', 'ampx', 'sandbox', 'delete', '--yes']);
})), TIMEOUT);
await shell.shell(['npx', 'ampx', 'sandbox', '--once'], {
modEnv: {
AWS_REGION: context.aws.region,
},
});
try {

// Future code goes here, putting the try/finally here already so it doesn't
// get forgotten.

} finally {
await shell.shell(['npx', 'ampx', 'sandbox', 'delete', '--yes'], {
modEnv: {
AWS_REGION: context.aws.region,
},
});
}
}), TIMEOUT);

async function updateCdkDependency(context: TemporaryDirectoryContext, cliVersion: string, libVersion: string) {
const filename = path.join(context.integTestDir, 'package.json');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TestContext } from '../../lib/integ-test';
import { AwsContext, withAws } from '../../lib/with-aws';
import { DisableBootstrapContext } from '../../lib/with-cdk-app';
import { PackageContext, withPackages } from '../../lib/with-packages';
import { TemporaryDirectoryContext, withTemporaryDirectory } from '../../lib/with-temporary-directory';

/**
* The default prerequisites for tests running tool integrations
*/
export function withToolContext<A extends TestContext>(
block: (context: A & TemporaryDirectoryContext & PackageContext & AwsContext & DisableBootstrapContext
) => Promise<void>) {
return withAws(withTemporaryDirectory(withPackages(block)));
}
7 changes: 7 additions & 0 deletions packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
*/
public abstract readonly ownerAccountId: string;

/**
* IPv4 CIDR provisioned under pool
* Required to check for overlapping CIDRs after provisioning
* is complete under IPAM pool
*/
public abstract readonly ipv4IpamProvisionedCidrs?: string[];

/**
* If this is set to true, don't error out on trying to select subnets
*/
Expand Down
52 changes: 23 additions & 29 deletions packages/@aws-cdk/aws-location-alpha/lib/place-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,39 +74,12 @@ export enum IntendedUse {
STORAGE = 'Storage',
}

abstract class PlaceIndexBase extends Resource implements IPlaceIndex {
public abstract readonly placeIndexName: string;
public abstract readonly placeIndexArn: string;

/**
* Grant the given principal identity permissions to perform the actions on this place index.
*/
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
return iam.Grant.addToPrincipal({
grantee: grantee,
actions: actions,
resourceArns: [this.placeIndexArn],
});
}

/**
* Grant the given identity permissions to search using this index
*/
public grantSearch(grantee: iam.IGrantable): iam.Grant {
return this.grant(grantee,
'geo:SearchPlaceIndexForPosition',
'geo:SearchPlaceIndexForSuggestions',
'geo:SearchPlaceIndexForText',
);
}
}

/**
* A Place Index
*
* @see https://docs.aws.amazon.com/location/latest/developerguide/places-concepts.html
*/
export class PlaceIndex extends PlaceIndexBase {
export class PlaceIndex extends Resource implements IPlaceIndex {
/**
* Use an existing place index by name
*/
Expand All @@ -130,7 +103,7 @@ export class PlaceIndex extends PlaceIndexBase {
throw new Error(`Place Index Arn ${placeIndexArn} does not have a resource name.`);
}

class Import extends PlaceIndexBase {
class Import extends Resource implements IPlaceIndex {
public readonly placeIndexName = parsedArn.resourceName!;
public readonly placeIndexArn = placeIndexArn;
}
Expand Down Expand Up @@ -187,4 +160,25 @@ export class PlaceIndex extends PlaceIndexBase {
this.placeIndexUpdateTime = placeIndex.attrUpdateTime;
}

/**
* Grant the given principal identity permissions to perform the actions on this place index.
*/
public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
return iam.Grant.addToPrincipal({
grantee: grantee,
actions: actions,
resourceArns: [this.placeIndexArn],
});
}

/**
* Grant the given identity permissions to search using this index
*/
public grantSearch(grantee: iam.IGrantable): iam.Grant {
return this.grant(grantee,
'geo:SearchPlaceIndexForPosition',
'geo:SearchPlaceIndexForSuggestions',
'geo:SearchPlaceIndexForText',
);
}
}
7 changes: 0 additions & 7 deletions packages/@aws-cdk/aws-scheduler-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ const cronBasedSchedule = new Schedule(this, 'Schedule', {
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,
and time zone in which EventBridge Scheduler evaluates the schedule.

[comment]: <> (TODO: Switch to `ts` once Schedule is implemented)

```ts
declare const target: targets.LambdaInvoke;

Expand Down Expand Up @@ -208,11 +206,6 @@ const target = new targets.LambdaInvoke(fn, {
});
```


### Cross-account and cross-region targets

Executing cross-account and cross-region targets are not supported yet.

### Specifying Encryption key

EventBridge Scheduler integrates with AWS Key Management Service (AWS KMS) to encrypt and decrypt your data using an AWS KMS key.
Expand Down
19 changes: 7 additions & 12 deletions packages/@aws-cdk/aws-scheduler-alpha/lib/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@ import { DefaultTokenResolver, IResolveContext, Stack, StringConcat, Token, Toke
import { ISchedule } from './schedule';

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

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

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

/**
* Escape hatch for other ContextAttribute that might be resolved in future.
* Escape hatch for other Context Attributes that may be added in the future
*
* @param name - name will replace xxx in <aws.scheduler.xxx>
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/aws-cdk-lib/core/lib/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export class App extends Stage {
if (autoSynth) {
// synth() guarantees it will only execute once, so a default of 'true'
// doesn't bite manual calling of the function.
process.once('beforeExit', () => this.synth());
process.once('beforeExit', () => this.synth({ errorOnDuplicateSynth: false }));
}

this._treeMetadata = props.treeMetadata ?? true;
Expand Down
61 changes: 60 additions & 1 deletion packages/aws-cdk-lib/core/lib/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ export class Stage extends Construct {
*/
private assembly?: cxapi.CloudAssembly;

/**
* The cached set of construct paths. Empty if assembly was not yet built.
*/
private constructPathsCache: Set<string>;

/**
* Validation plugins to run during synthesis. If any plugin reports any violation,
* synthesis will be interrupted and the report displayed to the user.
Expand All @@ -163,6 +168,7 @@ export class Stage extends Construct {

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

this.constructPathsCache = new Set<string>();
this.parentStage = Stage.of(this);

this.region = props.env?.region ?? this.parentStage?.region;
Expand Down Expand Up @@ -210,16 +216,62 @@ export class Stage extends Construct {
* calls will return the same assembly.
*/
public synth(options: StageSynthesisOptions = { }): cxapi.CloudAssembly {
if (!this.assembly || options.force) {

let newConstructPaths = this.listAllConstructPaths(this);

// If the assembly cache is uninitiazed, run synthesize and reset construct paths cache
if (this.constructPathsCache.size == 0 || !this.assembly || options.force) {
this.assembly = synthesize(this, {
skipValidation: options.skipValidation,
validateOnSynthesis: options.validateOnSynthesis,
});
newConstructPaths = this.listAllConstructPaths(this);
this.constructPathsCache = newConstructPaths;
}

// If the construct paths set has changed
if (!this.constructPathSetsAreEqual(this.constructPathsCache, newConstructPaths)) {
const errorMessage = 'Synthesis has been called multiple times and the construct tree was modified after the first synthesis.';
if (options.errorOnDuplicateSynth ?? true) {
throw new Error(errorMessage + ' This is not allowed. Remove multple synth() calls and do not modify the construct tree after the first synth().');
} else {
// eslint-disable-next-line no-console
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.');
}
}

// Reset construct paths cache
this.constructPathsCache = newConstructPaths;

return this.assembly;
}

// Function that lists all construct paths and returns them as a set
private listAllConstructPaths(construct: IConstruct): Set<string> {
const paths = new Set<string>();
function recurse(root: IConstruct) {
paths.add(root.node.path);
for (const child of root.node.children) {
if (!Stage.isStage(child)) {
recurse(child);
}
}
}
recurse(construct);
return paths;
}

// Checks if sets of construct paths are equal
private constructPathSetsAreEqual(set1: Set<string>, set2: Set<string>): boolean {
if (set1.size !== set2.size) return false;
for (const id of set1) {
if (!set2.has(id)) {
return false;
}
}
return true;
}

private createBuilder(outdir?: string) {
// cannot specify "outdir" if we are a nested stage
if (this.parentStage && outdir) {
Expand Down Expand Up @@ -259,4 +311,11 @@ export interface StageSynthesisOptions {
* @default false
*/
readonly force?: boolean;

/**
* Whether or not to throw a warning instead of an error if the construct tree has
* been mutated since the last synth.
* @default true
*/
readonly errorOnDuplicateSynth?: boolean;
}
25 changes: 25 additions & 0 deletions packages/aws-cdk-lib/core/test/synthesis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as os from 'os';
import * as path from 'path';
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
import { Construct } from 'constructs';
import { Template } from '../../assertions';
import * as cxschema from '../../cloud-assembly-schema';
import * as cxapi from '../../cx-api';
import * as cdk from '../lib';
Expand Down Expand Up @@ -362,6 +363,30 @@ describe('synthesis', () => {

});

test('calling synth multiple times errors if construct tree is mutated', () => {
const app = new cdk.App();

const stages = [
{
stage: 'PROD',
},
{
stage: 'BETA',
},
];

// THEN - no error the first time synth is called
let stack = new cdk.Stack(app, `${stages[0].stage}-Stack`, {});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();

// THEN - error is thrown since synth was called with mutated stack name
stack = new cdk.Stack(app, `${stages[1].stage}-Stack`, {});
expect(() => {
Template.fromStack(stack);
}).toThrow('Synthesis has been called multiple times and the construct tree was modified after the first synthesis');
});
});

function list(outdir: string) {
Expand Down
Loading

0 comments on commit b1d8a8f

Please sign in to comment.