Files
personal_site/frontend/node_modules/@aws-cdk/cloud-assembly-api/lib/cloud-assembly.js
T

374 lines
50 KiB
JavaScript
Raw Normal View History

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CloudAssemblyBuilder = exports.CloudAssembly = void 0;
const fs = require("fs");
const os = require("os");
const path = require("path");
const cxschema = require("@aws-cdk/cloud-assembly-schema/lib");
const cloudformation_artifact_1 = require("./artifacts/cloudformation-artifact");
const nested_cloud_assembly_artifact_1 = require("./artifacts/nested-cloud-assembly-artifact");
const tree_cloud_artifact_1 = require("./artifacts/tree-cloud-artifact");
const cloud_artifact_1 = require("./cloud-artifact");
const error_1 = require("./private/error");
const toposort_1 = require("./private/toposort");
const CLOUD_ASSEMBLY_SYMBOL = Symbol.for('@aws-cdk/cx-api.CloudAssembly');
/**
* The name of the root manifest file of the assembly.
*/
const MANIFEST_FILE = 'manifest.json';
/**
* Represents a deployable cloud application.
*/
class CloudAssembly {
/**
* Return whether the given object is a CloudAssembly.
*
* We do attribute detection since we can't reliably use 'instanceof'.
*/
static isCloudAssembly(x) {
return x !== null && typeof (x) === 'object' && CLOUD_ASSEMBLY_SYMBOL in x;
}
/**
* Cleans up any temporary assembly directories that got created in this process
*
* If a Cloud Assembly is emitted to a temporary directory, its directory gets
* added to a list. This function iterates over that list and deletes each
* directory in it, to free up disk space.
*
* This function will normally be called automatically during Node process
* exit and so you don't need to call this. However, some test environments do
* not properly trigger Node's `exit` event. Notably: Jest does not trigger
* the `exit` event (<https://github.com/jestjs/jest/issues/10927>).
*
* ## Cleaning up temporary directories in jest
*
* For Jest, you have to make sure this function is called at the end of the
* test suite instead:
*
* ```js
* import { CloudAssembly } from 'aws-cdk-lib/cx-api';
*
* afterAll(CloudAssembly.cleanupTemporaryDirectories);
* ```
*
* Alternatively, you can use the `setupFilesAfterEnv` feature and use a
* provided helper script to automatically inject the above into every
* test file, so you don't have to do it by hand.
*
* ```
* $ npx jest --setupFilesAfterEnv aws-cdk-lib/testhelpers/jest-autoclean
* ```
*
* Or put the following into `jest.config.js`:
*
* ```js
* module.exports = {
* // ...
* setupFilesAfterEnv: ['aws-cdk-lib/testhelpers/jest-cleanup'],
* };
* ```
*/
static cleanupTemporaryDirectories() {
for (const dir of TEMPORARY_ASSEMBLY_DIRS) {
fs.rmSync(dir, { recursive: true, force: true });
}
TEMPORARY_ASSEMBLY_DIRS.splice(0, TEMPORARY_ASSEMBLY_DIRS.length);
}
/**
* Reads a cloud assembly from the specified directory.
* @param directory - The root directory of the assembly.
*/
constructor(directory, loadOptions) {
this.directory = directory;
this.manifest = cxschema.Manifest.loadAssemblyManifest(path.join(directory, MANIFEST_FILE), loadOptions);
this.version = this.manifest.version;
this.artifacts = this.renderArtifacts(loadOptions?.topoSort ?? true);
this.runtime = this.manifest.runtime || { libraries: {} };
Object.defineProperty(this, CLOUD_ASSEMBLY_SYMBOL, { value: true });
// force validation of deps by accessing 'depends' on all artifacts
this.validateDeps();
}
/**
* Attempts to find an artifact with a specific identity.
* @returns A `CloudArtifact` object or `undefined` if the artifact does not exist in this assembly.
* @param id - The artifact ID
*/
tryGetArtifact(id) {
return this.artifacts.find(a => a.id === id);
}
/**
* Returns a CloudFormation stack artifact from this assembly.
*
* Will only search the current assembly.
*
* @param stackName - the name of the CloudFormation stack.
* @throws if there is no stack artifact by that name
* @throws if there is more than one stack with the same stack name. You can
* use `getStackArtifact(stack.artifactId)` instead.
* @returns a `CloudFormationStackArtifact` object.
*/
getStackByName(stackName) {
const artifacts = this.artifacts.filter(a => a instanceof cloudformation_artifact_1.CloudFormationStackArtifact && a.stackName === stackName);
if (!artifacts || artifacts.length === 0) {
throw new error_1.CloudAssemblyError(`Unable to find stack with stack name "${stackName}"`);
}
if (artifacts.length > 1) {
throw new error_1.CloudAssemblyError(`There are multiple stacks with the stack name "${stackName}" (${artifacts.map(a => a.id).join(',')}). Use "getStackArtifact(id)" instead`);
}
return artifacts[0];
}
/**
* Returns a CloudFormation stack artifact by name from this assembly.
* @deprecated renamed to `getStackByName` (or `getStackArtifact(id)`)
*/
getStack(stackName) {
return this.getStackByName(stackName);
}
/**
* Returns a CloudFormation stack artifact from this assembly.
*
* @param artifactId - the artifact id of the stack (can be obtained through `stack.artifactId`).
* @throws if there is no stack artifact with that id
* @returns a `CloudFormationStackArtifact` object.
*/
getStackArtifact(artifactId) {
const artifact = this.tryGetArtifactRecursively(artifactId);
if (!artifact) {
throw new error_1.CloudAssemblyError(`Unable to find artifact with id "${artifactId}"`);
}
if (!(artifact instanceof cloudformation_artifact_1.CloudFormationStackArtifact)) {
throw new error_1.CloudAssemblyError(`Artifact ${artifactId} is not a CloudFormation stack`);
}
return artifact;
}
tryGetArtifactRecursively(artifactId) {
return this.stacksRecursively.find(a => a.id === artifactId);
}
/**
* Returns all the stacks, including the ones in nested assemblies
*/
get stacksRecursively() {
function search(stackArtifacts, assemblies) {
if (assemblies.length === 0) {
return stackArtifacts;
}
const [head, ...tail] = assemblies;
const nestedAssemblies = head.nestedAssemblies.map(asm => asm.nestedAssembly);
return search(stackArtifacts.concat(head.stacks), tail.concat(nestedAssemblies));
}
return search([], [this]);
}
/**
* Returns a nested assembly artifact.
*
* @param artifactId - The artifact ID of the nested assembly
*/
getNestedAssemblyArtifact(artifactId) {
const artifact = this.tryGetArtifact(artifactId);
if (!artifact) {
throw new error_1.CloudAssemblyError(`Unable to find artifact with id "${artifactId}"`);
}
if (!(artifact instanceof nested_cloud_assembly_artifact_1.NestedCloudAssemblyArtifact)) {
throw new error_1.CloudAssemblyError(`Found artifact '${artifactId}' but it's not a nested cloud assembly`);
}
return artifact;
}
/**
* Returns a nested assembly.
*
* @param artifactId - The artifact ID of the nested assembly
*/
getNestedAssembly(artifactId) {
return this.getNestedAssemblyArtifact(artifactId).nestedAssembly;
}
/**
* Returns the tree metadata artifact from this assembly.
* @throws if there is no metadata artifact by that name
* @returns a `TreeCloudArtifact` object if there is one defined in the manifest, `undefined` otherwise.
*/
tree() {
const trees = this.artifacts.filter(a => a.manifest.type === cxschema.ArtifactType.CDK_TREE);
if (trees.length === 0) {
return undefined;
}
else if (trees.length > 1) {
throw new error_1.CloudAssemblyError(`Multiple artifacts of type ${cxschema.ArtifactType.CDK_TREE} found in manifest`);
}
const tree = trees[0];
if (!(tree instanceof tree_cloud_artifact_1.TreeCloudArtifact)) {
throw new error_1.CloudAssemblyError('"Tree" artifact is not of expected type');
}
return tree;
}
/**
* @returns all the CloudFormation stack artifacts that are included in this assembly.
*/
get stacks() {
return this.artifacts.filter(isCloudFormationStackArtifact);
function isCloudFormationStackArtifact(x) {
return x instanceof cloudformation_artifact_1.CloudFormationStackArtifact;
}
}
/**
* The nested assembly artifacts in this assembly
*/
get nestedAssemblies() {
return this.artifacts.filter(isNestedCloudAssemblyArtifact);
function isNestedCloudAssemblyArtifact(x) {
return x instanceof nested_cloud_assembly_artifact_1.NestedCloudAssemblyArtifact;
}
}
validateDeps() {
for (const artifact of this.artifacts) {
ignore(artifact.dependencies);
}
}
renderArtifacts(topoSort) {
const result = new Array();
for (const [name, artifact] of Object.entries(this.manifest.artifacts || {})) {
const cloudartifact = cloud_artifact_1.CloudArtifact.fromManifest(this, name, artifact);
if (cloudartifact) {
result.push(cloudartifact);
}
}
return topoSort ? (0, toposort_1.topologicalSort)(result, x => x.id, x => x._dependencyIDs) : result;
}
}
exports.CloudAssembly = CloudAssembly;
/**
* Can be used to build a cloud assembly.
*/
class CloudAssemblyBuilder {
/**
* Initializes a cloud assembly builder.
* @param outdir - The output directory, uses temporary directory if undefined
*/
constructor(outdir, props = {}) {
this.artifacts = {};
this.missing = new Array();
this.outdir = determineOutputDirectory(outdir);
this.assetOutdir = props.assetOutdir ?? this.outdir;
this.parentBuilder = props.parentBuilder;
// we leverage the fact that outdir is long-lived to avoid staging assets into it
// that were already staged (copying can be expensive). this is achieved by the fact
// that assets use a source hash as their name. other artifacts, and the manifest itself,
// will overwrite existing files as needed.
ensureDirSync(this.outdir);
}
/**
* Adds an artifact into the cloud assembly.
* @param id - The ID of the artifact.
* @param manifest - The artifact manifest
*/
addArtifact(id, manifest) {
this.artifacts[id] = filterUndefined(manifest);
}
/**
* Reports that some context is missing in order for this cloud assembly to be fully synthesized.
* @param missing - Missing context information.
*/
addMissing(missing) {
if (this.missing.every(m => m.key !== missing.key)) {
this.missing.push(missing);
}
// Also report in parent
this.parentBuilder?.addMissing(missing);
}
/**
* Finalizes the cloud assembly into the output directory returns a
* `CloudAssembly` object that can be used to inspect the assembly.
*/
buildAssembly(options = {}) {
// explicitly initializing this type will help us detect
// breaking changes. (For example adding a required property will break compilation).
let manifest = {
version: cxschema.Manifest.version(),
artifacts: this.artifacts,
runtime: options.runtimeInfo,
missing: this.missing.length > 0 ? this.missing : undefined,
};
// now we can filter
manifest = filterUndefined(manifest);
const manifestFilePath = path.join(this.outdir, MANIFEST_FILE);
cxschema.Manifest.saveAssemblyManifest(manifest, manifestFilePath);
// "backwards compatibility": in order for the old CLI to tell the user they
// need a new version, we'll emit the legacy manifest with only "version".
// this will result in an error "CDK Toolkit >= CLOUD_ASSEMBLY_VERSION is required in order to interact with this program."
fs.writeFileSync(path.join(this.outdir, 'cdk.out'), JSON.stringify({ version: manifest.version }));
return new CloudAssembly(this.outdir);
}
/**
* Creates a nested cloud assembly
*/
createNestedAssembly(artifactId, displayName) {
const directoryName = artifactId;
const innerAsmDir = path.join(this.outdir, directoryName);
this.addArtifact(artifactId, {
type: cxschema.ArtifactType.NESTED_CLOUD_ASSEMBLY,
properties: {
directoryName,
displayName,
},
});
return new CloudAssemblyBuilder(innerAsmDir, {
// Reuse the same asset output directory as the current Casm builder
assetOutdir: this.assetOutdir,
parentBuilder: this,
});
}
/**
* Delete the cloud assembly directory
*/
delete() {
fs.rmSync(this.outdir, { recursive: true, force: true });
}
}
exports.CloudAssemblyBuilder = CloudAssemblyBuilder;
/**
* Returns a copy of `obj` without undefined values in maps or arrays.
*/
function filterUndefined(obj) {
if (Array.isArray(obj)) {
return obj.filter(x => x !== undefined).map(x => filterUndefined(x));
}
if (typeof (obj) === 'object') {
const ret = {};
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
continue;
}
ret[key] = filterUndefined(value);
}
return ret;
}
return obj;
}
function ignore(_x) {
return;
}
/**
* Turn the given optional output directory into a fixed output directory
*/
function determineOutputDirectory(outdir) {
if (outdir) {
return outdir;
}
// Make a temporary directory; clean it up automatically if this is done for testing.
const tmpDir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'cdk.out'));
TEMPORARY_ASSEMBLY_DIRS.push(tmpDir);
return outdir ?? tmpDir;
}
function ensureDirSync(dir) {
if (fs.existsSync(dir)) {
if (!fs.statSync(dir).isDirectory()) {
throw new error_1.CloudAssemblyError(`${dir} must be a directory`);
}
}
else {
fs.mkdirSync(dir, { recursive: true });
}
}
// On process exit, delete all temporary assembly directories
const TEMPORARY_ASSEMBLY_DIRS = [];
process.on('exit', () => CloudAssembly.cleanupTemporaryDirectories());
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xvdWQtYXNzZW1ibHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjbG91ZC1hc3NlbWJseS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSx5QkFBeUI7QUFDekIseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUU3QiwrREFBK0Q7QUFDL0QsaUZBQWtGO0FBQ2xGLCtGQUF5RjtBQUN6Rix5RUFBb0U7QUFDcEUscURBQWlEO0FBQ2pELDJDQUFxRDtBQUNyRCxpREFBcUQ7QUFFckQsTUFBTSxxQkFBcUIsR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLCtCQUErQixDQUFDLENBQUM7QUFFMUU7O0dBRUc7QUFDSCxNQUFNLGFBQWEsR0FBRyxlQUFlLENBQUM7QUFFdEM7O0dBRUc7QUFDSCxNQUFhLGFBQWE7SUFDeEI7Ozs7T0FJRztJQUNJLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBTTtRQUNsQyxPQUFPLENBQUMsS0FBSyxJQUFJLElBQUksT0FBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLFFBQVEsSUFBSSxxQkFBcUIsSUFBSSxDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7T0F1Q0c7SUFDSSxNQUFNLENBQUMsMkJBQTJCO1FBQ3ZDLEtBQUssTUFBTSxHQUFHLElBQUksdUJBQXVCLEVBQUUsQ0FBQztZQUMxQyxFQUFFLENBQUMsTUFBTSxDQUFDLEdBQUcsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7UUFDbkQsQ0FBQztRQUNELHVCQUF1QixDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsdUJBQXVCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDcEUsQ0FBQztJQTJCRDs7O09BR0c7SUFDSCxZQUFZLFNBQWlCLEVBQUUsV0FBMEM7UUFDdkUsSUFBSSxDQUFDLFNBQVMsR0FBRyxTQUFTLENBQUM7UUFFM0IsSUFBSSxDQUFDLFFB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