-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
524 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/exec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import * as child_process from 'node:child_process'; | ||
import { ToolkitError } from '../../errors'; | ||
|
||
interface ExecOptions { | ||
extraEnv?: { [key: string]: string | undefined }; | ||
cwd?: string; | ||
} | ||
|
||
/** | ||
* Execute a command and args in a child process | ||
*/ | ||
export async function execInChildProcess(commandAndArgs: string, options: ExecOptions = {}) { | ||
return new Promise<void>((ok, fail) => { | ||
// We use a slightly lower-level interface to: | ||
// | ||
// - Pass arguments in an array instead of a string, to get around a | ||
// number of quoting issues introduced by the intermediate shell layer | ||
// (which would be different between Linux and Windows). | ||
// | ||
// - Inherit stderr from controlling terminal. We don't use the captured value | ||
// anyway, and if the subprocess is printing to it for debugging purposes the | ||
// user gets to see it sooner. Plus, capturing doesn't interact nicely with some | ||
// processes like Maven. | ||
const proc = child_process.spawn(commandAndArgs, { | ||
stdio: ['ignore', 'inherit', 'inherit'], | ||
detached: false, | ||
shell: true, | ||
cwd: options.cwd, | ||
env: { | ||
...process.env, | ||
...(options.extraEnv ?? {}), | ||
}, | ||
}); | ||
|
||
proc.on('error', fail); | ||
|
||
proc.on('exit', code => { | ||
if (code === 0) { | ||
return ok(); | ||
} else { | ||
return fail(new ToolkitError(`Subprocess exited with error ${code}`)); | ||
} | ||
}); | ||
}); | ||
} |
63 changes: 0 additions & 63 deletions
63
packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/from-app.ts
This file was deleted.
Oops, something went wrong.
179 changes: 179 additions & 0 deletions
179
packages/@aws-cdk/toolkit/lib/api/cloud-assembly/private/prepare-source.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import * as os from 'node:os'; | ||
import * as path from 'node:path'; | ||
import * as cxschema from '@aws-cdk/cloud-assembly-schema'; | ||
import * as cxapi from '@aws-cdk/cx-api'; | ||
import { prepareDefaultEnvironment as oldPrepare, prepareContext, spaceAvailableForContext } from 'aws-cdk/lib/api/cxapp/exec'; | ||
import { Settings } from 'aws-cdk/lib/settings'; | ||
import { loadTree, some } from 'aws-cdk/lib/tree'; | ||
import { splitBySize } from 'aws-cdk/lib/util'; | ||
import { versionNumber } from 'aws-cdk/lib/version'; | ||
import * as fs from 'fs-extra'; | ||
import { lte } from 'semver'; | ||
import type { AppSynthOptions } from './source-builder'; | ||
import { ToolkitError } from '../../errors'; | ||
import { ActionAwareIoHost, asLogger, error, warn } from '../../io/private'; | ||
import { ToolkitServices } from '../../toolkit/private'; | ||
|
||
export { guessExecutable } from 'aws-cdk/lib/api/cxapp/exec'; | ||
|
||
type Env = { [key: string]: string }; | ||
type Context = { [key: string]: any }; | ||
|
||
/** | ||
* If we don't have region/account defined in context, we fall back to the default SDK behavior | ||
* where region is retrieved from ~/.aws/config and account is based on default credentials provider | ||
* chain and then STS is queried. | ||
* | ||
* This is done opportunistically: for example, if we can't access STS for some reason or the region | ||
* is not configured, the context value will be 'null' and there could failures down the line. In | ||
* some cases, synthesis does not require region/account information at all, so that might be perfectly | ||
* fine in certain scenarios. | ||
* | ||
* @param context The context key/value bash. | ||
*/ | ||
export async function prepareDefaultEnvironment(services: ToolkitServices, props: { outdir?: string } = {}): Promise<Env> { | ||
const logFn = asLogger(services.ioHost, 'ASSEMBLY').debug; | ||
const env = await oldPrepare(services.sdkProvider, logFn); | ||
|
||
if (props.outdir) { | ||
env[cxapi.OUTDIR_ENV] = props.outdir; | ||
await logFn('outdir:', props.outdir); | ||
} | ||
|
||
// CLI version information | ||
env[cxapi.CLI_ASM_VERSION_ENV] = cxschema.Manifest.version(); | ||
env[cxapi.CLI_VERSION_ENV] = versionNumber(); | ||
|
||
await logFn('env:', env); | ||
return env; | ||
} | ||
|
||
/** | ||
* Run code from a different working directory | ||
*/ | ||
export async function changeDir<T>(block: () => Promise<T>, workingDir?: string) { | ||
const originalWorkingDir = process.cwd(); | ||
try { | ||
if (workingDir) { | ||
process.chdir(workingDir); | ||
} | ||
|
||
return await block(); | ||
|
||
} finally { | ||
if (workingDir) { | ||
process.chdir(originalWorkingDir); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Run code with additional environment variables | ||
*/ | ||
export async function withEnv<T>(env: Env = {}, block: () => Promise<T>) { | ||
const originalEnv = process.env; | ||
try { | ||
process.env = { | ||
...originalEnv, | ||
...env, | ||
}; | ||
|
||
return await block(); | ||
|
||
} finally { | ||
process.env = originalEnv; | ||
} | ||
} | ||
|
||
/** | ||
* Run code with context setup inside the environment | ||
*/ | ||
export async function withContext<T>( | ||
inputContext: Context, | ||
env: Env, | ||
synthOpts: AppSynthOptions = {}, | ||
block: (env: Env, context: Context) => Promise<T>, | ||
) { | ||
const context = await prepareContext(synthOptsDefaults(synthOpts), inputContext, env); | ||
let contextOverflowLocation = null; | ||
|
||
try { | ||
const envVariableSizeLimit = os.platform() === 'win32' ? 32760 : 131072; | ||
const [smallContext, overflow] = splitBySize(context, spaceAvailableForContext(env, envVariableSizeLimit)); | ||
|
||
// Store the safe part in the environment variable | ||
env[cxapi.CONTEXT_ENV] = JSON.stringify(smallContext); | ||
|
||
// If there was any overflow, write it to a temporary file | ||
if (Object.keys(overflow ?? {}).length > 0) { | ||
const contextDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-context')); | ||
contextOverflowLocation = path.join(contextDir, 'context-overflow.json'); | ||
fs.writeJSONSync(contextOverflowLocation, overflow); | ||
env[cxapi.CONTEXT_OVERFLOW_LOCATION_ENV] = contextOverflowLocation; | ||
} | ||
|
||
// call the block code with new environment | ||
return await block(env, context); | ||
} finally { | ||
if (contextOverflowLocation) { | ||
fs.removeSync(path.dirname(contextOverflowLocation)); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Checks if a given assembly supports context overflow, warn otherwise. | ||
* | ||
* @param assembly the assembly to check | ||
*/ | ||
export async function checkContextOverflowSupport(assembly: cxapi.CloudAssembly, ioHost: ActionAwareIoHost): Promise<void> { | ||
const logFn = asLogger(ioHost, 'ASSEMBLY').warn; | ||
const tree = loadTree(assembly); | ||
const frameworkDoesNotSupportContextOverflow = some(tree, node => { | ||
const fqn = node.constructInfo?.fqn; | ||
const version = node.constructInfo?.version; | ||
return (fqn === 'aws-cdk-lib.App' && version != null && lte(version, '2.38.0')) // v2 | ||
|| fqn === '@aws-cdk/core.App'; // v1 | ||
}); | ||
|
||
// We're dealing with an old version of the framework here. It is unaware of the temporary | ||
// file, which means that it will ignore the context overflow. | ||
if (frameworkDoesNotSupportContextOverflow) { | ||
await logFn('Part of the context could not be sent to the application. Please update the AWS CDK library to the latest version.'); | ||
} | ||
} | ||
|
||
/** | ||
* Safely create an assembly from a cloud assembly directory | ||
*/ | ||
export async function assemblyFromDirectory(assemblyDir: string, ioHost: ActionAwareIoHost) { | ||
try { | ||
const assembly = new cxapi.CloudAssembly(assemblyDir, { | ||
// We sort as we deploy | ||
topoSort: false, | ||
}); | ||
await checkContextOverflowSupport(assembly, ioHost); | ||
return assembly; | ||
|
||
} catch (err: any) { | ||
if (err.message.includes(cxschema.VERSION_MISMATCH)) { | ||
// this means the CLI version is too old. | ||
// we instruct the user to upgrade. | ||
const message = 'This AWS CDK Toolkit is not compatible with the AWS CDK library used by your application. Please upgrade to the latest version.'; | ||
await ioHost.notify(error(message, 'CDK_ASSEMBLY_E1111', { error: err.message })); | ||
throw new ToolkitError(`${message}\n(${err.message}`); | ||
} | ||
throw err; | ||
} | ||
} | ||
function synthOptsDefaults(synthOpts: AppSynthOptions = {}): Settings { | ||
return new Settings({ | ||
debug: false, | ||
pathMetadata: true, | ||
versionReporting: true, | ||
assetMetadata: true, | ||
assetStaging: true, | ||
...synthOpts, | ||
}, true); | ||
} | ||
|
Oops, something went wrong.