Skip to content

Commit

Permalink
more stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
mrgrain committed Jan 14, 2025
1 parent aa34e88 commit e76260b
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { MissingContext } from '@aws-cdk/cloud-assembly-schema';
import * as cxapi from '@aws-cdk/cx-api';
import { ICloudAssemblySource } from './types';
import * as contextproviders from '../../context-providers';
import { debug } from '../../logging';
import { Context, PROJECT_CONTEXT } from '../../settings';
import { SdkProvider } from '../aws-auth';
import { ToolkitError } from '../errors';

export interface CloudExecutableProps {
/**
* AWS object (used by contextprovider)
*/
readonly sdkProvider: SdkProvider;

/**
* Application context
*/
readonly context: Context;

/**
* The file used to store application context in (relative to cwd).
*
* @default "cdk.context.json"
*/
readonly contextFile?: string;

/**
* Enable context lookups.
*
* Producing a `cxapi.CloudAssembly` will fail if this is disabled and context lookups need to be performed.
*
* @default true
*/
readonly lookups?: boolean;
}

/**
* Represent the Cloud Executable and the synthesis we can do on it
*/
export class ContextAwareCloudAssembly implements ICloudAssemblySource {
private canLookup: boolean;
private context: Context;
private contextFile: string;

constructor(private readonly source: ICloudAssemblySource, private readonly props: CloudExecutableProps) {
this.canLookup = props.lookups ?? true;
this.context = props.context;
this.contextFile = props.contextFile ?? PROJECT_CONTEXT; // @todo new feature not needed right now
}

/**
* 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
// 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;
while (true) {
const assembly = await this.source.produce();

if (assembly.manifest.missing && assembly.manifest.missing.length > 0) {
const missingKeys = missingContextKeys(assembly.manifest.missing);

if (!this.canLookup) {
throw new ToolkitError(
'Context lookups have been disabled. '
+ 'Make sure all necessary context is already in \'cdk.context.json\' by running \'cdk synth\' on a machine with sufficient AWS credentials and committing the result. '
+ `Missing context keys: '${Array.from(missingKeys).join(', ')}'`);
}

let tryLookup = true;
if (previouslyMissingKeys && equalSets(missingKeys, previouslyMissingKeys)) {
debug('Not making progress trying to resolve environmental context. Giving up.');
tryLookup = false;
}

previouslyMissingKeys = missingKeys;

if (tryLookup) {
debug('Some context information is missing. Fetching...');

await contextproviders.provideContextValues(
assembly.manifest.missing,
this.context,
this.props.sdkProvider,
);

// Cache the new context to disk
await this.context.save(this.contextFile);

// Execute again
continue;
}
}

return assembly;
}
}

}

/**
* Return all keys of missing context items
*/
function missingContextKeys(missing?: MissingContext[]): Set<string> {
return new Set((missing || []).map(m => m.key));
}

/**
* Are two sets equal to each other
*/
function equalSets<A>(a: Set<A>, b: Set<A>) {
if (a.size !== b.size) { return false; }
for (const x of a) {
if (!b.has(x)) { return false; }
}
return true;
}
63 changes: 63 additions & 0 deletions packages/@aws-cdk/toolkit/lib/api/cloud-assembly/from-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ToolkitError } from '../errors';
import { ContextAwareCloudAssembly } from './context-aware-source';
import { ICloudAssemblySource } from './types';

/**
* Configuration for creating a CLI from an AWS CDK App directory
*/
export interface FromCdkAppProps {
/**
* @default - current working directory
*/
readonly workingDirectory?: string;

/**
* Emits the synthesized cloud assembly into a directory
*
* @default cdk.out
*/
readonly output?: string;
}

/**
* Use a directory containing an AWS CDK app as source.
* @param directory the directory of the AWS CDK app. Defaults to the current working directory.
* @param props additional configuration properties
* @returns an instance of `AwsCdkCli`
*/
export function fromCdkApp(app: string, props: FromCdkAppProps = {}): ICloudAssemblySource {
return new ContextAwareCloudAssembly(
{
produce: async () => {
await this.lock?.release();

try {
const build = this.props.configuration.settings.get(['build']);
if (build) {
await exec(build, { cwd: props.workingDirectory });
}

const commandLine = await guessExecutable(app);
const outdir = props.output ?? 'cdk.out';

try {
fs.mkdirpSync(outdir);
} catch (e: any) {
throw new ToolkitError(`Could not create output directory at '${outdir}' (${e.message}).`);
}

this.lock = await new RWLock(outdir).acquireWrite();

const env = await prepareDefaultEnvironment(this.props.sdkProvider, { outdir });
return await withContext(env, this.props.configuration, async (envWithContext, _context) => {
await exec(commandLine.join(' '), { extraEnv: envWithContext, cwd: props.workingDirectory });
return createAssembly(outdir);
});
} finally {
await this.lock?.release();
}
},
},
this.propsForContextAssembly,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import type * as cxapi from '@aws-cdk/cx-api';
import { ICloudAssemblySource } from './types';

/**
* A CloudAssemblySource that is caching its result once produced.
*
* Most Toolkit interactions should use a cached source.
* Not caching is relevant when the source changes frequently
* and it is to expensive to predict if the source has changed.
* A CloudAssemblySource that is representing a already existing and produced CloudAssembly.
*/
export class IdentityCloudAssemblySource implements ICloudAssemblySource {
public constructor(private readonly cloudAssembly: cxapi.CloudAssembly) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import * as cxapi from '@aws-cdk/cx-api';
import * as chalk from 'chalk';
import { minimatch } from 'minimatch';
import * as semver from 'semver';
import { ICloudAssemblySource } from './types';
import { StackSelectionStrategy, StackSelector } from '../../types';
import { flatten } from '../../util';
import { ToolkitError } from '../errors';

/**
* A single Cloud Assembly wrapped to provide additional stack operations.
*/
export class StackAssembly {
export class StackAssembly implements ICloudAssemblySource {
/**
* The directory this CloudAssembly was read from
*/
Expand All @@ -19,6 +20,10 @@ export class StackAssembly {
this.directory = assembly.directory;
}

public async produce(): Promise<cxapi.CloudAssembly> {
return this.assembly;
}

public async selectStacks(selector: StackSelector, validate: MetadataMessageOptions | false): Promise<StackCollection> {
const stacks = await this.selectStacksFromSelector(selector);
if (validate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type * as cxapi from '@aws-cdk/cx-api';

export interface ICloudAssemblySource {
/**
* produce
* Produce a CloudAssembly from the current source
*/
produce(): Promise<cxapi.CloudAssembly>;
}
6 changes: 3 additions & 3 deletions packages/@aws-cdk/toolkit/lib/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { obscureTemplate, SynthOptions } from './actions/synth';
import { WatchOptions } from './actions/watch';
import { SdkOptions } from './api/aws-auth/sdk';
import { StackAssembly, StackCollection } from './api/cloud-assembly/stack-assembly';
import { CachedCloudAssemblySource } from './api/cloud-assembly-source/cached-source';
import { IdentityCloudAssemblySource } from './api/cloud-assembly-source/identity';
import { ICloudAssemblySource } from './api/cloud-assembly-source/types';
import { CachedCloudAssemblySource } from './api/cloud-assembly/cached-source';
import { IdentityCloudAssemblySource } from './api/cloud-assembly/identity-source';
import { ICloudAssemblySource } from './api/cloud-assembly/types';
import { ToolkitError } from './api/errors';
import { IIoHost } from './io-host';
import { StackSelectionStrategy, ToolkitAction } from './types';
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/toolkit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/jsonfile": "^6.1.4",
"@types/node": "^18.18.14",
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"jest": "^29.7.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/toolkit/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
},
"include": ["**/*.ts"],
"exclude": ["node_modules", "**/*.d.ts", "dist"],
"references": [{ "path": "../cx-api" }]
"references": [{ "path": "../cx-api" }, { "path": "../cloudformation-diff" }]
}

0 comments on commit e76260b

Please sign in to comment.