@aws-cdk/core
Version:
AWS Cloud Development Kit Core Library
917 lines • 128 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const cxapi = require("@aws-cdk/cx-api");
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const assets_1 = require("./assets");
const construct_compat_1 = require("./construct-compat");
const context_provider_1 = require("./context-provider");
const asset_parameters_1 = require("./private/asset-parameters");
const cloudformation_lang_1 = require("./private/cloudformation-lang");
const logical_id_1 = require("./private/logical-id");
const resolve_1 = require("./private/resolve");
const uniqueid_1 = require("./private/uniqueid");
const STACK_SYMBOL = Symbol.for('@aws-cdk/core.Stack');
const MY_STACK_CACHE = Symbol.for('@aws-cdk/core.Stack.myStack');
const VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/;
/**
* The well-known name for the docker image asset ECR repository. All docker
* image assets will be pushed into this repository with an image tag based on
* the source hash.
*/
const ASSETS_ECR_REPOSITORY_NAME = "aws-cdk/assets";
/**
* This allows users to work around the fact that the ECR repository is
* (currently) not configurable by setting this context key to their desired
* repository name. The CLI will auto-create this ECR repository if it's not
* already created.
*/
const ASSETS_ECR_REPOSITORY_NAME_OVERRIDE_CONTEXT_KEY = "assets-ecr-repository-name";
/**
* A root construct which represents a single CloudFormation stack.
*/
class Stack extends construct_compat_1.Construct {
/**
* Creates a new stack.
*
* @param scope Parent of this stack, usually a Program instance.
* @param id The construct ID of this stack. If `stackName` is not explicitly
* defined, this id (and any parent IDs) will be used to determine the
* physical ID of the stack.
* @param props Stack properties.
*/
constructor(scope, id, props = {}) {
// For unit test convenience parents are optional, so bypass the type check when calling the parent.
super(scope, id);
/**
* Options for CloudFormation template (like version, transform, description).
*/
this.templateOptions = {};
/**
* Other stacks this stack depends on
*/
this._stackDependencies = {};
/**
* Lists all missing contextual information.
* This is returned when the stack is synthesized under the 'missing' attribute
* and allows tooling to obtain the context and re-synthesize.
*/
this._missingContext = new Array();
/**
* The image ID of all the docker image assets that were already added to this
* stack (to avoid duplication).
*/
this.addedImageAssets = new Set();
Object.defineProperty(this, STACK_SYMBOL, { value: true });
this._logicalIds = new logical_id_1.LogicalIDs();
const { account, region, environment } = this.parseEnvironment(props.env);
this.account = account;
this.region = region;
this.environment = environment;
if (props.description !== undefined) {
// Max length 1024 bytes
// Typically 2 bytes per character, may be more for more exotic characters
if (props.description.length > 512) {
throw new Error(`Stack description must be <= 1024 bytes. Received description: '${props.description}'`);
}
this.templateOptions.description = props.description;
}
this._stackName = props.stackName !== undefined ? props.stackName : this.generateUniqueId();
this.tags = new tag_manager_1.TagManager(cfn_resource_1.TagType.KEY_VALUE, 'aws:cdk:stack', props.tags);
if (!VALID_STACK_NAME_REGEX.test(this.stackName)) {
throw new Error(`Stack name must match the regular expression: ${VALID_STACK_NAME_REGEX.toString()}, got '${this.stackName}'`);
}
// the preferred behavior is to generate a unique id for this stack and use
// it as the artifact ID in the assembly. this allows multiple stacks to use
// the same name. however, this behavior is breaking for 1.x so it's only
// applied under a feature flag which is applied automatically for new
// projects created using `cdk init`.
this.artifactId = this.node.tryGetContext(cxapi.ENABLE_STACK_NAME_DUPLICATES_CONTEXT)
? this.generateUniqueId()
: this.stackName;
this.templateFile = `${this.artifactId}.template.json`;
this.templateUrl = lazy_1.Lazy.stringValue({ produce: () => this._templateUrl || '<unresolved>' });
}
/**
* Return whether the given object is a Stack.
*
* We do attribute detection since we can't reliably use 'instanceof'.
*/
static isStack(x) {
return x !== null && typeof (x) === 'object' && STACK_SYMBOL in x;
}
/**
* Looks up the first stack scope in which `construct` is defined. Fails if there is no stack up the tree.
* @param construct The construct to start the search from.
*/
static of(construct) {
// we want this to be as cheap as possible. cache this result by mutating
// the object. anecdotally, at the time of this writing, @aws-cdk/core unit
// tests hit this cache 1,112 times, @aws-cdk/aws-cloudformation unit tests
// hit this 2,435 times).
const cache = construct[MY_STACK_CACHE];
if (cache) {
return cache;
}
else {
const value = _lookup(construct);
Object.defineProperty(construct, MY_STACK_CACHE, {
enumerable: false,
writable: false,
configurable: false,
value
});
return value;
}
function _lookup(c) {
if (Stack.isStack(c)) {
return c;
}
if (!c.node.scope) {
throw new Error(`No stack could be identified for the construct at path ${construct.node.path}`);
}
return _lookup(c.node.scope);
}
}
/**
* Resolve a tokenized value in the context of the current stack.
*/
resolve(obj) {
return resolve_1.resolve(obj, {
scope: this,
prefix: [],
resolver: cloudformation_lang_1.CLOUDFORMATION_TOKEN_RESOLVER,
preparing: false
});
}
/**
* Convert an object, potentially containing tokens, to a JSON string
*/
toJsonString(obj, space) {
return cloudformation_lang_1.CloudFormationLang.toJSON(obj, space).toString();
}
/**
* Indicate that a context key was expected
*
* Contains instructions which will be emitted into the cloud assembly on how
* the key should be supplied.
*
* @param report The set of parameters needed to obtain the context
*/
reportMissingContext(report) {
this._missingContext.push(report);
}
/**
* Rename a generated logical identities
*
* To modify the naming scheme strategy, extend the `Stack` class and
* override the `createNamingScheme` method.
*/
renameLogicalId(oldId, newId) {
this._logicalIds.addRename(oldId, newId);
}
/**
* Allocates a stack-unique CloudFormation-compatible logical identity for a
* specific resource.
*
* This method is called when a `CfnElement` is created and used to render the
* initial logical identity of resources. Logical ID renames are applied at
* this stage.
*
* This method uses the protected method `allocateLogicalId` to render the
* logical ID for an element. To modify the naming scheme, extend the `Stack`
* class and override this method.
*
* @param element The CloudFormation element for which a logical identity is
* needed.
*/
getLogicalId(element) {
const logicalId = this.allocateLogicalId(element);
return this._logicalIds.applyRename(logicalId);
}
/**
* Add a dependency between this stack and another stack.
*
* This can be used to define dependencies between any two stacks within an
* app, and also supports nested stacks.
*/
addDependency(target, reason) {
deps_1.addDependency(this, target, reason);
}
/**
* Return the stacks this stack depends on
*/
get dependencies() {
return Object.values(this._stackDependencies).map(x => x.stack);
}
/**
* The concrete CloudFormation physical stack name.
*
* This is either the name defined explicitly in the `stackName` prop or
* allocated based on the stack's location in the construct tree. Stacks that
* are directly defined under the app use their construct `id` as their stack
* name. Stacks that are defined deeper within the tree will use a hashed naming
* scheme based on the construct path to ensure uniqueness.
*
* If you wish to obtain the deploy-time AWS::StackName intrinsic,
* you can use `Aws.stackName` directly.
*/
get stackName() {
return this._stackName;
}
/**
* The partition in which this stack is defined
*/
get partition() {
// Always return a non-scoped partition intrinsic. These will usually
// be used to construct an ARN, but there are no cross-partition
// calls anyway.
return cfn_pseudo_1.Aws.PARTITION;
}
/**
* The Amazon domain suffix for the region in which this stack is defined
*/
get urlSuffix() {
// Since URL Suffix always follows partition, it is unscoped like partition is.
return cfn_pseudo_1.Aws.URL_SUFFIX;
}
/**
* The ID of the stack
*
* @example After resolving, looks like arn:aws:cloudformation:us-west-2:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123
*/
get stackId() {
return new cfn_pseudo_1.ScopedAws(this).stackId;
}
/**
* Returns the list of notification Amazon Resource Names (ARNs) for the current stack.
*/
get notificationArns() {
return new cfn_pseudo_1.ScopedAws(this).notificationArns;
}
/**
* Indicates if this is a nested stack, in which case `parentStack` will include a reference to it's parent.
*/
get nested() {
return this.nestedStackResource !== undefined;
}
/**
* Creates an ARN from components.
*
* If `partition`, `region` or `account` are not specified, the stack's
* partition, region and account will be used.
*
* If any component is the empty string, an empty string will be inserted
* into the generated ARN at the location that component corresponds to.
*
* The ARN will be formatted as follows:
*
* arn:{partition}:{service}:{region}:{account}:{resource}{sep}}{resource-name}
*
* The required ARN pieces that are omitted will be taken from the stack that
* the 'scope' is attached to. If all ARN pieces are supplied, the supplied scope
* can be 'undefined'.
*/
formatArn(components) {
return arn_1.Arn.format(components, this);
}
/**
* Given an ARN, parses it and returns components.
*
* If the ARN is a concrete string, it will be parsed and validated. The
* separator (`sep`) will be set to '/' if the 6th component includes a '/',
* in which case, `resource` will be set to the value before the '/' and
* `resourceName` will be the rest. In case there is no '/', `resource` will
* be set to the 6th components and `resourceName` will be set to the rest
* of the string.
*
* If the ARN includes tokens (or is a token), the ARN cannot be validated,
* since we don't have the actual value yet at the time of this function
* call. You will have to know the separator and the type of ARN. The
* resulting `ArnComponents` object will contain tokens for the
* subexpressions of the ARN, not string literals. In this case this
* function cannot properly parse the complete final resourceName (path) out
* of ARNs that use '/' to both separate the 'resource' from the
* 'resourceName' AND to subdivide the resourceName further. For example, in
* S3 ARNs:
*
* arn:aws:s3:::my_corporate_bucket/path/to/exampleobject.png
*
* After parsing the resourceName will not contain
* 'path/to/exampleobject.png' but simply 'path'. This is a limitation
* because there is no slicing functionality in CloudFormation templates.
*
* @param arn The ARN string to parse
* @param sepIfToken The separator used to separate resource from resourceName
* @param hasName Whether there is a name component in the ARN at all. For
* example, SNS Topics ARNs have the 'resource' component contain the topic
* name, and no 'resourceName' component.
*
* @returns an ArnComponents object which allows access to the various
* components of the ARN.
*
* @returns an ArnComponents object which allows access to the various
* components of the ARN.
*/
parseArn(arn, sepIfToken = '/', hasName = true) {
return arn_1.Arn.parse(arn, sepIfToken, hasName);
}
/**
* Returnst the list of AZs that are availability in the AWS environment
* (account/region) associated with this stack.
*
* If the stack is environment-agnostic (either account and/or region are
* tokens), this property will return an array with 2 tokens that will resolve
* at deploy-time to the first two availability zones returned from CloudFormation's
* `Fn::GetAZs` intrinsic function.
*
* If they are not available in the context, returns a set of dummy values and
* reports them as missing, and let the CLI resolve them by calling EC2
* `DescribeAvailabilityZones` on the target environment.
*/
get availabilityZones() {
// if account/region are tokens, we can't obtain AZs through the context
// provider, so we fallback to use Fn::GetAZs. the current lowest common
// denominator is 2 AZs across all AWS regions.
const agnostic = token_1.Token.isUnresolved(this.account) || token_1.Token.isUnresolved(this.region);
if (agnostic) {
return this.node.tryGetContext(cxapi.AVAILABILITY_ZONE_FALLBACK_CONTEXT_KEY) || [
cfn_fn_1.Fn.select(0, cfn_fn_1.Fn.getAzs()),
cfn_fn_1.Fn.select(1, cfn_fn_1.Fn.getAzs())
];
}
const value = context_provider_1.ContextProvider.getValue(this, {
provider: cxapi.AVAILABILITY_ZONE_PROVIDER,
dummyValue: ['dummy1a', 'dummy1b', 'dummy1c'],
}).value;
if (!Array.isArray(value)) {
throw new Error(`Provider ${cxapi.AVAILABILITY_ZONE_PROVIDER} expects a list`);
}
return value;
}
addFileAsset(asset) {
// assets are always added at the top-level stack
if (this.nestedStackParent) {
return this.nestedStackParent.addFileAsset(asset);
}
let params = this.assetParameters.node.tryFindChild(asset.sourceHash);
if (!params) {
params = new asset_parameters_1.FileAssetParameters(this.assetParameters, asset.sourceHash);
const metadata = {
path: asset.fileName,
id: asset.sourceHash,
packaging: asset.packaging,
sourceHash: asset.sourceHash,
s3BucketParameter: params.bucketNameParameter.logicalId,
s3KeyParameter: params.objectKeyParameter.logicalId,
artifactHashParameter: params.artifactHashParameter.logicalId,
};
this.node.addMetadata(cxapi.ASSET_METADATA, metadata);
}
const bucketName = params.bucketNameParameter.valueAsString;
// key is prefix|postfix
const encodedKey = params.objectKeyParameter.valueAsString;
const s3Prefix = cfn_fn_1.Fn.select(0, cfn_fn_1.Fn.split(cxapi.ASSET_PREFIX_SEPARATOR, encodedKey));
const s3Filename = cfn_fn_1.Fn.select(1, cfn_fn_1.Fn.split(cxapi.ASSET_PREFIX_SEPARATOR, encodedKey));
const objectKey = `${s3Prefix}${s3Filename}`;
const s3Url = `https://s3.${this.region}.${this.urlSuffix}/${bucketName}/${objectKey}`;
return { bucketName, objectKey, s3Url };
}
addDockerImageAsset(asset) {
var _a, _b;
if (this.nestedStackParent) {
return this.nestedStackParent.addDockerImageAsset(asset);
}
// check if we have an override from context
const repositoryNameOverride = this.node.tryGetContext(ASSETS_ECR_REPOSITORY_NAME_OVERRIDE_CONTEXT_KEY);
const repositoryName = (_b = (_a = asset.repositoryName) !== null && _a !== void 0 ? _a : repositoryNameOverride) !== null && _b !== void 0 ? _b : ASSETS_ECR_REPOSITORY_NAME;
const imageTag = asset.sourceHash;
const assetId = asset.sourceHash;
// only add every image (identified by source hash) once for each stack that uses it.
if (!this.addedImageAssets.has(assetId)) {
const metadata = {
repositoryName,
imageTag,
id: assetId,
packaging: 'container-image',
path: asset.directoryName,
sourceHash: asset.sourceHash,
buildArgs: asset.dockerBuildArgs,
target: asset.dockerBuildTarget,
file: asset.dockerFile,
};
this.node.addMetadata(cxapi.ASSET_METADATA, metadata);
this.addedImageAssets.add(assetId);
}
return {
imageUri: `${this.account}.dkr.ecr.${this.region}.${this.urlSuffix}/${repositoryName}:${imageTag}`,
repositoryName
};
}
/**
* If this is a nested stack, returns it's parent stack.
*/
get nestedStackParent() {
return this.nestedStackResource && Stack.of(this.nestedStackResource);
}
/**
* Returns the parent of a nested stack.
*
* @deprecated use `nestedStackParent`
*/
get parentStack() {
return this.nestedStackParent;
}
/**
* Add a Transform to this stack. A Transform is a macro that AWS
* CloudFormation uses to process your template.
*
* Duplicate values are removed when stack is synthesized.
*
* @example addTransform('AWS::Serverless-2016-10-31')
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/transform-section-structure.html
*
* @param transform The transform to add
*/
addTransform(transform) {
if (!this.templateOptions.transforms) {
this.templateOptions.transforms = [];
}
this.templateOptions.transforms.push(transform);
}
/**
* Called implicitly by the `addDependency` helper function in order to
* realize a dependency between two top-level stacks at the assembly level.
*
* Use `stack.addDependency` to define the dependency between any two stacks,
* and take into account nested stack relationships.
*
* @internal
*/
_addAssemblyDependency(target, reason) {
// defensive: we should never get here for nested stacks
if (this.nested || target.nested) {
throw new Error(`Cannot add assembly-level dependencies for nested stacks`);
}
reason = reason || 'dependency added using stack.addDependency()';
const cycle = target.stackDependencyReasons(this);
if (cycle !== undefined) {
// tslint:disable-next-line:max-line-length
throw new Error(`'${target.node.path}' depends on '${this.node.path}' (${cycle.join(', ')}). Adding this dependency (${reason}) would create a cyclic reference.`);
}
let dep = this._stackDependencies[target.node.uniqueId];
if (!dep) {
dep = this._stackDependencies[target.node.uniqueId] = {
stack: target,
reasons: []
};
}
dep.reasons.push(reason);
if (process.env.CDK_DEBUG_DEPS) {
// tslint:disable-next-line:no-console
console.error(`[CDK_DEBUG_DEPS] stack "${this.node.path}" depends on "${target.node.path}" because: ${reason}`);
}
}
/**
* Returns the naming scheme used to allocate logical IDs. By default, uses
* the `HashedAddressingScheme` but this method can be overridden to customize
* this behavior.
*
* In order to make sure logical IDs are unique and stable, we hash the resource
* construct tree path (i.e. toplevel/secondlevel/.../myresource) and add it as
* a suffix to the path components joined without a separator (CloudFormation
* IDs only allow alphanumeric characters).
*
* The result will be:
*
* <path.join('')><md5(path.join('/')>
* "human" "hash"
*
* If the "human" part of the ID exceeds 240 characters, we simply trim it so
* the total ID doesn't exceed CloudFormation's 255 character limit.
*
* We only take 8 characters from the md5 hash (0.000005 chance of collision).
*
* Special cases:
*
* - If the path only contains a single component (i.e. it's a top-level
* resource), we won't add the hash to it. The hash is not needed for
* disamiguation and also, it allows for a more straightforward migration an
* existing CloudFormation template to a CDK stack without logical ID changes
* (or renames).
* - For aesthetic reasons, if the last components of the path are the same
* (i.e. `L1/L2/Pipeline/Pipeline`), they will be de-duplicated to make the
* resulting human portion of the ID more pleasing: `L1L2Pipeline<HASH>`
* instead of `L1L2PipelinePipeline<HASH>`
* - If a component is named "Default" it will be omitted from the path. This
* allows refactoring higher level abstractions around constructs without affecting
* the IDs of already deployed resources.
* - If a component is named "Resource" it will be omitted from the user-visible
* path, but included in the hash. This reduces visual noise in the human readable
* part of the identifier.
*
* @param cfnElement The element for which the logical ID is allocated.
*/
allocateLogicalId(cfnElement) {
const scopes = cfnElement.node.scopes;
const stackIndex = scopes.indexOf(cfnElement.stack);
const pathComponents = scopes.slice(stackIndex + 1).map(x => x.node.id);
return uniqueid_1.makeUniqueId(pathComponents);
}
/**
* Validate stack name
*
* CloudFormation stack names can include dashes in addition to the regular identifier
* character classes, and we don't allow one of the magic markers.
*
* @internal
*/
_validateId(name) {
if (name && !VALID_STACK_NAME_REGEX.test(name)) {
throw new Error(`Stack name must match the regular expression: ${VALID_STACK_NAME_REGEX.toString()}, got '${name}'`);
}
}
/**
* Prepare stack
*
* Find all CloudFormation references and tell them we're consuming them.
*
* Find all dependencies as well and add the appropriate DependsOn fields.
*/
prepare() {
const tokens = this.findTokens();
// References (originating from this stack)
for (const reference of tokens) {
// skip if this is not a CfnReference
if (!cfn_reference_1.CfnReference.isCfnReference(reference)) {
continue;
}
const targetStack = Stack.of(reference.target);
// skip if this is not a cross-stack reference
if (targetStack === this) {
continue;
}
// determine which stack should create the cross reference
const factory = this.determineCrossReferenceFactory(targetStack);
// if one side is a nested stack (has "parentStack"), we let it create the reference
// since it has more knowledge about the world.
const consumedValue = factory.prepareCrossReference(this, reference);
// if the reference has already been assigned a value for the consuming stack, carry on.
if (!reference.hasValueForStack(this)) {
reference.assignValueForStack(this, consumedValue);
}
}
// Resource dependencies
for (const dependency of this.node.dependencies) {
for (const target of findCfnResources([dependency.target])) {
for (const source of findCfnResources([dependency.source])) {
source.addDependsOn(target);
}
}
}
if (this.tags.hasTags()) {
this.node.addMetadata(cxapi.STACK_TAGS_METADATA_KEY, this.tags.renderTags());
}
if (this.nestedStackParent) {
// add the nested stack template as an asset
const cfn = JSON.stringify(this._toCloudFormation());
const templateHash = crypto.createHash('sha256').update(cfn).digest('hex');
const parent = this.nestedStackParent;
const templateLocation = parent.addFileAsset({
packaging: assets_1.FileAssetPackaging.FILE,
sourceHash: templateHash,
fileName: this.templateFile
});
// if bucketName/objectKey are cfn parameters from a stack other than the parent stack, they will
// be resolved as cross-stack references like any other (see "multi" tests).
this._templateUrl = `https://s3.${parent.region}.${parent.urlSuffix}/${templateLocation.bucketName}/${templateLocation.objectKey}`;
}
}
synthesize(session) {
const builder = session.assembly;
// write the CloudFormation template as a JSON file
const outPath = path.join(builder.outdir, this.templateFile);
const text = JSON.stringify(this._toCloudFormation(), undefined, 2);
fs.writeFileSync(outPath, text);
for (const ctx of this._missingContext) {
builder.addMissing(ctx);
}
// if this is a nested stack, do not emit it as a cloud assembly artifact (it will be registered as an s3 asset instead)
if (this.nested) {
return;
}
const deps = this.dependencies.map(s => s.artifactId);
const meta = this.collectMetadata();
// backwards compatibility since originally artifact ID was always equal to
// stack name the stackName attribute is optional and if it is not specified
// the CLI will use the artifact ID as the stack name. we *could have*
// always put the stack name here but wanted to minimize the risk around
// changes to the assembly manifest. so this means that as long as stack
// name and artifact ID are the same, the cloud assembly manifest will not
// change.
const stackNameProperty = this.stackName === this.artifactId
? {}
: { stackName: this.stackName };
const properties = {
templateFile: this.templateFile,
...stackNameProperty
};
// add an artifact that represents this stack
builder.addArtifact(this.artifactId, {
type: cxapi.ArtifactType.AWS_CLOUDFORMATION_STACK,
environment: this.environment,
properties,
dependencies: deps.length > 0 ? deps : undefined,
metadata: Object.keys(meta).length > 0 ? meta : undefined,
});
}
/**
* Returns the CloudFormation template for this stack by traversing
* the tree and invoking _toCloudFormation() on all Entity objects.
*
* @internal
*/
_toCloudFormation() {
let transform;
if (this.templateOptions.transform) {
// tslint:disable-next-line: max-line-length
this.node.addWarning('This stack is using the deprecated `templateOptions.transform` property. Consider switching to `addTransform()`.');
this.addTransform(this.templateOptions.transform);
}
if (this.templateOptions.transforms) {
if (this.templateOptions.transforms.length === 1) { // Extract single value
transform = this.templateOptions.transforms[0];
}
else { // Remove duplicate values
transform = Array.from(new Set(this.templateOptions.transforms));
}
}
const template = {
Description: this.templateOptions.description,
Transform: transform,
AWSTemplateFormatVersion: this.templateOptions.templateFormatVersion,
Metadata: this.templateOptions.metadata
};
const elements = cfnElements(this);
const fragments = elements.map(e => this.resolve(e._toCloudFormation()));
// merge in all CloudFormation fragments collected from the tree
for (const fragment of fragments) {
merge(template, fragment);
}
// resolve all tokens and remove all empties
const ret = this.resolve(template) || {};
this._logicalIds.assertAllRenamesApplied();
return ret;
}
/**
* Exports a resolvable value for use in another stack.
*
* @returns a token that can be used to reference the value from the producing stack.
*/
prepareCrossReference(sourceStack, reference) {
const targetStack = Stack.of(reference.target);
// Ensure a singleton "Exports" scoping Construct
// This mostly exists to trigger LogicalID munging, which would be
// disabled if we parented constructs directly under Stack.
// Also it nicely prevents likely construct name clashes
const exportsScope = targetStack.getCreateExportsScope();
// Ensure a singleton CfnOutput for this value
const resolved = targetStack.resolve(reference);
const id = 'Output' + JSON.stringify(resolved);
const exportName = targetStack.generateExportName(exportsScope, id);
const output = exportsScope.node.tryFindChild(id);
if (!output) {
new cfn_output_1.CfnOutput(exportsScope, id, { value: token_1.Token.asString(reference), exportName });
}
// add a dependency on the producing stack - it has to be deployed before this stack can consume the exported value
// if the producing stack is a nested stack (i.e. has a parent), the dependency is taken on the parent.
const producerDependency = targetStack.nestedStackParent ? targetStack.nestedStackParent : targetStack;
const consumerDependency = sourceStack.nestedStackParent ? sourceStack.nestedStackParent : sourceStack;
consumerDependency.addDependency(producerDependency, `${sourceStack.node.path} -> ${reference.target.node.path}.${reference.displayName}`);
// We want to return an actual FnImportValue Token here, but Fn.importValue() returns a 'string',
// so construct one in-place.
return new intrinsic_1.Intrinsic({ 'Fn::ImportValue': exportName });
}
getCreateExportsScope() {
const exportsName = 'Exports';
let stackExports = this.node.tryFindChild(exportsName);
if (stackExports === undefined) {
stackExports = new construct_compat_1.Construct(this, exportsName);
}
return stackExports;
}
/**
* Determine the various stack environment attributes.
*
*/
parseEnvironment(env = {}) {
// if an environment property is explicitly specified when the stack is
// created, it will be used. if not, use tokens for account and region but
// they do not need to be scoped, the only situation in which
// export/fn::importvalue would work if { Ref: "AWS::AccountId" } is the
// same for provider and consumer anyway.
const account = env.account || cfn_pseudo_1.Aws.ACCOUNT_ID;
const region = env.region || cfn_pseudo_1.Aws.REGION;
// this is the "aws://" env specification that will be written to the cloud assembly
// manifest. it will use "unknown-account" and "unknown-region" to indicate
// environment-agnosticness.
const envAccount = !token_1.Token.isUnresolved(account) ? account : cxapi.UNKNOWN_ACCOUNT;
const envRegion = !token_1.Token.isUnresolved(region) ? region : cxapi.UNKNOWN_REGION;
return {
account, region,
environment: cxapi.EnvironmentUtils.format(envAccount, envRegion)
};
}
/**
* Check whether this stack has a (transitive) dependency on another stack
*
* Returns the list of reasons on the dependency path, or undefined
* if there is no dependency.
*/
stackDependencyReasons(other) {
if (this === other) {
return [];
}
for (const dep of Object.values(this._stackDependencies)) {
const ret = dep.stack.stackDependencyReasons(other);
if (ret !== undefined) {
return [...dep.reasons, ...ret];
}
}
return undefined;
}
collectMetadata() {
const output = {};
const stack = this;
visit(this);
return output;
function visit(node) {
// break off if we reached a node that is not a child of this stack
const parent = findParentStack(node);
if (parent !== stack) {
return;
}
if (node.node.metadata.length > 0) {
// Make the path absolute
output[construct_compat_1.ConstructNode.PATH_SEP + node.node.path] = node.node.metadata.map(md => stack.resolve(md));
}
for (const child of node.node.children) {
visit(child);
}
}
function findParentStack(node) {
if (node instanceof Stack && node.nestedStackParent === undefined) {
return node;
}
if (!node.node.scope) {
return undefined;
}
return findParentStack(node.node.scope);
}
}
/**
* Calculcate the stack name based on the construct path
*/
generateUniqueId() {
// In tests, it's possible for this stack to be the root object, in which case
// we need to use it as part of the root path.
const rootPath = this.node.scope !== undefined ? this.node.scopes.slice(1) : [this];
const ids = rootPath.map(c => c.node.id);
// Special case, if rootPath is length 1 then just use ID (backwards compatibility)
// otherwise use a unique stack name (including hash). This logic is already
// in makeUniqueId, *however* makeUniqueId will also strip dashes from the name,
// which *are* allowed and also used, so we short-circuit it.
if (ids.length === 1) {
// Could be empty in a unit test, so just pretend it's named "Stack" then
return ids[0] || 'Stack';
}
return uniqueid_1.makeUniqueId(ids);
}
generateExportName(stackExports, id) {
const stack = Stack.of(stackExports);
const components = [...stackExports.node.scopes.slice(2).map(c => c.node.id), id];
const prefix = stack.stackName ? stack.stackName + ':' : '';
const exportName = prefix + uniqueid_1.makeUniqueId(components);
return exportName;
}
get assetParameters() {
if (!this._assetParameters) {
this._assetParameters = new construct_compat_1.Construct(this, 'AssetParameters');
}
return this._assetParameters;
}
determineCrossReferenceFactory(target) {
// unsupported: stacks from different apps
if (target.node.root !== this.node.root) {
throw new Error(`Cannot reference across apps. ` +
`Consuming and producing stacks must be defined within the same CDK app.`);
}
// unsupported: stacks are not in the same environment
if (target.environment !== this.environment) {
throw new Error(`Stack "${this.node.path}" cannot consume a cross reference from stack "${target.node.path}". ` +
`Cross stack references are only supported for stacks deployed to the same environment or between nested stacks and their parent stack`);
}
// if one of the stacks is a nested stack, go ahead and give it the right to make the cross reference
if (target.nested) {
return target;
}
if (this.nested) {
return this;
}
// both stacks are top-level (non-nested), the taret (producing stack) gets to make the reference
return target;
}
/**
* Returns all the tokens used within the scope of the current stack.
*/
findTokens() {
const tokens = new Array();
for (const element of cfnElements(this)) {
try {
tokens.push(...resolve_1.findTokens(element, () => element._toCloudFormation()));
}
catch (e) {
// Note: it might be that the properties of the CFN object aren't valid.
// This will usually be preventatively caught in a construct's validate()
// and turned into a nicely descriptive error, but we're running prepare()
// before validate(). Swallow errors that occur because the CFN layer
// doesn't validate completely.
//
// This does make the assumption that the error will not be rectified,
// but the error will be thrown later on anyway. If the error doesn't
// get thrown down the line, we may miss references.
if (e.type === 'CfnSynthesisError') {
continue;
}
throw e;
}
}
return tokens;
}
}
exports.Stack = Stack;
function merge(template, part) {
for (const section of Object.keys(part)) {
const src = part[section];
// create top-level section if it doesn't exist
let dest = template[section];
if (!dest) {
template[section] = dest = src;
}
else {
// add all entities from source section to destination section
for (const id of Object.keys(src)) {
if (id in dest) {
throw new Error(`section '${section}' already contains '${id}'`);
}
dest[id] = src[id];
}
}
}
}
/**
* Collect all CfnElements from a Stack
*
* @param node Root node to collect all CfnElements from
* @param into Array to append CfnElements to
* @returns The same array as is being collected into
*/
function cfnElements(node, into = []) {
if (cfn_element_1.CfnElement.isCfnElement(node)) {
into.push(node);
}
for (const child of node.node.children) {
// Don't recurse into a substack
if (Stack.isStack(child)) {
continue;
}
cfnElements(child, into);
}
return into;
}
// These imports have to be at the end to prevent circular imports
const arn_1 = require("./arn");
const cfn_element_1 = require("./cfn-element");
const cfn_fn_1 = require("./cfn-fn");
const cfn_output_1 = require("./cfn-output");
const cfn_pseudo_1 = require("./cfn-pseudo");
const cfn_resource_1 = require("./cfn-resource");
const deps_1 = require("./deps");
const lazy_1 = require("./lazy");
const cfn_reference_1 = require("./private/cfn-reference");
const intrinsic_1 = require("./private/intrinsic");
const tag_manager_1 = require("./tag-manager");
const token_1 = require("./token");
/**
* Find all resources in a set of constructs
*/
function findCfnResources(roots) {
const ret = new Array();
for (const root of roots) {
ret.push(...root.node.findAll().filter(cfn_resource_1.CfnResource.isCfnResource));
}
return ret;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhY2suanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJzdGFjay50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLHlDQUF5QztBQUN6QyxpQ0FBaUM7QUFDakMseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixxQ0FBcUk7QUFDckkseURBQTZGO0FBQzdGLHlEQUFxRDtBQUVyRCxpRUFBaUU7QUFDakUsdUVBQWtHO0FBQ2xHLHFEQUFrRDtBQUNsRCwrQ0FBeUQ7QUFDekQsaURBQWtEO0FBRWxELE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMscUJBQXFCLENBQUMsQ0FBQztBQUN2RCxNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLDZCQUE2QixDQUFDLENBQUM7QUFFakUsTUFBTSxzQkFBc0IsR0FBRyx5QkFBeUIsQ0FBQztBQUV6RDs7OztHQUlHO0FBQ0gsTUFBTSwwQkFBMEIsR0FBRyxnQkFBZ0IsQ0FBQztBQUVwRDs7Ozs7R0FLRztBQUNILE1BQU0sK0NBQStDLEdBQUcsNEJBQTRCLENBQUM7QUFpQ3JGOztHQUVHO0FBQ0gsTUFBYSxLQUFNLFNBQVEsNEJBQVM7SUErS2xDOzs7Ozs7OztPQVFHO0lBQ0gsWUFBbUIsS0FBaUIsRUFBRSxFQUFXLEVBQUUsUUFBb0IsRUFBRTtRQUN2RSxvR0FBb0c7UUFDcEcsS0FBSyxDQUFDLEtBQU0sRUFBRSxFQUFHLENBQUMsQ0FBQztRQXZJckI7O1dBRUc7UUFDYSxvQkFBZSxHQUFxQixFQUFFLENBQUM7UUErRnZEOztXQUVHO1FBQ2MsdUJBQWtCLEdBQTRDLEVBQUcsQ0FBQztRQUVuRjs7OztXQUlHO1FBQ2Msb0JBQWUsR0FBRyxJQUFJLEtBQUssRUFBd0IsQ0FBQztRQVVyRTs7O1dBR0c7UUFDYyxxQkFBZ0IsR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFDO1FBZXBELE1BQU0sQ0FBQyxjQUFjLENBQUMsSUFBSSxFQUFFLFlBQVksRUFBRSxFQUFFLEtBQUssRUFBRSxJQUFJ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