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

chore(toolkit): pass context to cx builders #32996

Merged
merged 1 commit into from
Jan 17, 2025
Merged
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 @@ -55,8 +55,8 @@ export class ContextAwareCloudAssembly implements ICloudAssemblySource {
* Produce a Cloud Assembly, i.e. a set of stacks
*/
public async produce(): Promise<cxapi.CloudAssembly> {
// We may need to run the cloud executable multiple times in order to satisfy all missing context
// (When the executable runs, it will tell us about context it wants to use
// We may need to run the cloud assembly source multiple times in order to satisfy all missing context
// (When the source producer runs, it will tell us about context it wants to use
// but it missing. We'll then look up the context and run the executable again, and
// again, until it doesn't complain anymore or we've stopped making progress).
let previouslyMissingKeys: Set<string> | undefined;
Expand All @@ -82,14 +82,20 @@ export class ContextAwareCloudAssembly implements ICloudAssemblySource {
previouslyMissingKeys = missingKeys;

if (tryLookup) {
await this.ioHost.notify(debug('Some context information is missing. Fetching...'));
await this.ioHost.notify(debug('Some context information is missing. Fetching...', 'CDK_ASSEMBLY_I0241', {
missingKeys: Array.from(missingKeys),
}));
await contextproviders.provideContextValues(
assembly.manifest.missing,
this.context,
this.props.services.sdkProvider,
);

// Cache the new context to disk
await this.ioHost.notify(debug(`Writing updated context to ${this.contextFile}...`, 'CDK_ASSEMBLY_I0042', {
contextFile: this.contextFile,
context: this.context.all,
}));
await this.context.save(this.contextFile);

// Execute again
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export async function assemblyFromDirectory(assemblyDir: string, ioHost: ActionA
throw err;
}
}

function synthOptsDefaults(synthOpts: AppSynthOptions = {}): Settings {
return new Settings({
debug: false,
Expand All @@ -179,4 +180,3 @@ function synthOptsDefaults(synthOpts: AppSynthOptions = {}): Settings {
...synthOpts,
}, true);
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ContextAwareCloudAssembly, ContextAwareCloudAssemblyProps } from './con
import { execInChildProcess } from './exec';
import { assemblyFromDirectory, changeDir, determineOutputDirectory, guessExecutable, prepareDefaultEnvironment, withContext, withEnv } from './prepare-source';
import { ToolkitServices } from '../../../toolkit/private';
import { Context, ILock, RWLock } from '../../aws-cdk';
import { Context, ILock, RWLock, Settings } from '../../aws-cdk';
import { ToolkitError } from '../../errors';
import { debug } from '../../io/private';
import { AssemblyBuilder, CdkAppSourceProps } from '../source-builder';
Expand All @@ -28,7 +28,7 @@ export abstract class CloudAssemblySourceBuilder {
props: CdkAppSourceProps = {},
): Promise<ICloudAssemblySource> {
const services = await this.toolkitServices();
const context = new Context(); // @todo check if this needs to read anything
const context = new Context({ bag: new Settings(props.context ?? {}) });
const contextAssemblyProps: ContextAwareCloudAssemblyProps = {
services,
context,
Expand Down Expand Up @@ -85,7 +85,8 @@ export abstract class CloudAssemblySourceBuilder {
*/
public async fromCdkApp(app: string, props: CdkAppSourceProps = {}): Promise<ICloudAssemblySource> {
const services: ToolkitServices = await this.toolkitServices();
const context = new Context(); // @todo this definitely needs to read files
// @todo this definitely needs to read files from the CWD
const context = new Context({ bag: new Settings(props.context ?? {}) });
const contextAssemblyProps: ContextAwareCloudAssemblyProps = {
services,
context,
Expand Down
13 changes: 13 additions & 0 deletions packages/@aws-cdk/toolkit/lib/api/cloud-assembly/source-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export type AssemblyBuilder = (props: AppProps) => Promise<cxapi.CloudAssembly>;
*/
export interface CdkAppSourceProps {
/**
* Execute the application in this working directory.
*
* @default - current working directory
*/
readonly workingDirectory?: string;
Expand All @@ -39,6 +41,17 @@ export interface CdkAppSourceProps {
*/
readonly lookups?: boolean;

/**
* Context values for the application.
*
* Context can be read in the app from any construct using `node.getContext(key)`.
*
* @default - no context
*/
readonly context?: {
[key: string]: any;
};

/**
* Options that are passed through the context to a CDK app on synth
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as core from 'aws-cdk-lib/core';

const app = new core.App();
const stack = new core.Stack(app, 'Stack1');
new s3.Bucket(stack, 'MyBucket', {
bucketName: app.node.tryGetContext('externally-provided-bucket-name')
});
app.synth();
11 changes: 11 additions & 0 deletions packages/@aws-cdk/toolkit/test/_fixtures/external-context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as core from 'aws-cdk-lib/core';

export default async () => {
const app = new core.App();
const stack = new core.Stack(app, 'Stack1');
new s3.Bucket(stack, 'MyBucket', {
bucketName: app.node.tryGetContext('externally-provided-bucket-name'),
});
return app.synth() as any;
};
6 changes: 4 additions & 2 deletions packages/@aws-cdk/toolkit/test/_helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@ function fixturePath(...parts: string[]): string {
return path.normalize(path.join(__dirname, '..', '_fixtures', ...parts));
}

export async function appFixture(toolkit: Toolkit, name: string) {
export async function appFixture(toolkit: Toolkit, name: string, context?: { [key: string]: any }) {
const appPath = fixturePath(name, 'app.js');
if (!fs.existsSync(appPath)) {
throw new Error(`App Fixture ${name} does not exist in ${appPath}`);
}
const app = `cat ${appPath} | node --input-type=module`;
return toolkit.fromCdkApp(app, {
outdir: determineOutputDirectory(),
context,
});
}

export function builderFixture(toolkit: Toolkit, name: string) {
export function builderFixture(toolkit: Toolkit, name: string, context?: { [key: string]: any }) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const builder = require(path.join(__dirname, '..', '_fixtures', name)).default;
return toolkit.fromAssemblyBuilder(builder, {
outdir: determineOutputDirectory(),
context,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,53 @@ describe('fromAssemblyBuilder', () => {
test('defaults', async () => {
// WHEN
const cx = await builderFixture(toolkit, 'two-empty-stacks');
const cached = await toolkit.synth(cx);
const assembly = await cached.produce();
const assembly = await cx.produce();

// THEN
expect(assembly.stacksRecursively.map(s => s.hierarchicalId)).toEqual(['Stack1', 'Stack2']);
});

test('can provide context', async () => {
// WHEN
const cx = await builderFixture(toolkit, 'external-context', {
'externally-provided-bucket-name': 'amzn-s3-demo-bucket',
});
const assembly = await cx.produce();
const stack = assembly.getStackByName('Stack1').template;

// THEN
expect(JSON.stringify(stack)).toContain('amzn-s3-demo-bucket');
});
});

describe('fromAssemblyDirectory', () => {
describe('fromCdkApp', () => {
test('defaults', async () => {
// WHEN
const cx = await cdkOutFixture(toolkit, 'two-empty-stacks');
const cached = await toolkit.synth(cx);
const assembly = await cached.produce();
const cx = await appFixture(toolkit, 'two-empty-stacks');
const assembly = await cx.produce();

// THEN
expect(assembly.stacksRecursively.map(s => s.hierarchicalId)).toEqual(['Stack1', 'Stack2']);
});

test('can provide context', async () => {
// WHEN
const cx = await appFixture(toolkit, 'external-context', {
'externally-provided-bucket-name': 'amzn-s3-demo-bucket',
});
const assembly = await cx.produce();
const stack = assembly.getStackByName('Stack1').template;

// THEN
expect(JSON.stringify(stack)).toContain('amzn-s3-demo-bucket');
});
});

describe('fromCdkApp', () => {
describe('fromAssemblyDirectory', () => {
test('defaults', async () => {
// WHEN
const cx = await appFixture(toolkit, 'two-empty-stacks');
const cached = await toolkit.synth(cx);
const assembly = await cached.produce();
const cx = await cdkOutFixture(toolkit, 'two-empty-stacks');
const assembly = await cx.produce();

// THEN
expect(assembly.stacksRecursively.map(s => s.hierarchicalId)).toEqual(['Stack1', 'Stack2']);
Expand Down
Loading