UNPKG

@aws-cdk/core

Version:

AWS Cloud Development Kit Core Library

917 lines 128 kB
"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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