diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index 73bc9764ec296..105dd795667e1 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -22,6 +22,7 @@ enables organizations to create and manage catalogs of products for their end us - [Product](#product) - [Creating a product from a local asset](#creating-a-product-from-local-asset) - [Creating a product from a stack](#creating-a-product-from-a-stack) + - [Using Assets in your Product Stack](#using-aseets-in-your-product-stack) - [Creating a Product from a stack with a history of previous versions](#creating-a-product-from-a-stack-with-a-history-of-all-previous-versions) - [Adding a product to a portfolio](#adding-a-product-to-a-portfolio) - [TagOptions](#tag-options) @@ -185,6 +186,108 @@ const product = new servicecatalog.CloudFormationProduct(this, 'Product', { }); ``` +### Using Assets in your Product Stack + +You can reference assets in a Product Stack. For example, we can add a handler to a Lambda function or a S3 Asset directly from a local asset file. +In this case, you must provide a S3 Bucket with a bucketName to store your assets. + +```ts +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import { Bucket } from "@aws-cdk/aws-s3"; + +class LambdaProduct extends servicecatalog.ProductStack { + constructor(scope: Construct, id: string) { + super(scope, id); + + new lambda.Function(this, 'LambdaProduct', { + runtime: lambda.Runtime.PYTHON_3_9, + code: lambda.Code.fromAsset("./assets"), + handler: 'index.handler' + }); + } +} + +const userDefinedBucket = new Bucket(this, `UserDefinedBucket`, { + bucketName: 'user-defined-bucket-for-product-stack-assets', +}); + +const product = new servicecatalog.CloudFormationProduct(this, 'Product', { + productName: "My Product", + owner: "Product Owner", + productVersions: [ + { + productVersionName: "v1", + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new LambdaProduct(this, 'LambdaFunctionProduct', { + assetBucket: userDefinedBucket, + })), + }, + ], +}); +``` + +When a product containing an asset is shared with a spoke account, the corresponding asset bucket +will automatically grant read permissions to the spoke account. +Note, it is not recommended using a referenced bucket as permissions cannot be added from CDK. +In this case, it will be your responsibility to grant read permissions for the asset bucket to +the spoke account. +If you want to provide your own bucket policy or scope down your bucket policy further to only allow +reads from a specific launch role, refer to the following example policy: + +```ts +new iam.PolicyStatement({ + actions: [ + 's3:GetObject*', + 's3:GetBucket*', + 's3:List*', ], + effect: iam.Effect.ALLOW, + resources: [ + bucket.bucketArn, + bucket.arnForObjects('*'), + ], + principals: [ + new iam.ArnPrincipal(cdk.Stack.of(this).formatArn({ + service: 'iam', + region: '', + sharedAccount, + resource: 'role', + resourceName: launchRoleName, + })) + ], + conditions: { + 'ForAnyValue:StringEquals': { + 'aws:CalledVia': ['cloudformation.amazonaws.com'], + }, + 'Bool': { + 'aws:ViaAWSService': true, + }, + }, +}); +``` + +Furthermore, in order for a spoke account to provision a product with an asset, the role launching +the product needs permissions to read from the asset bucket. +We recommend you utilize a launch role with permissions to read from the asset bucket. +For example your launch role would need to include at least the following policy: + +```json +{ + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject" + ], + "Resource": "*" + } + ] +} +``` + +Please refer to [Set launch role](#set-launch-role) for additional details about launch roles. +See [Launch Constraint](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/constraints-launch.html) documentation +to understand the permissions that launch roles need. + ### Creating a Product from a stack with a history of previous versions The default behavior of Service Catalog is to overwrite each product version upon deployment. diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts b/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts index 07af9b2cc822d..d424d1a1ffcde 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts @@ -1,3 +1,4 @@ +import { IBucket } from '@aws-cdk/aws-s3'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import { Construct } from 'constructs'; import { hashValues } from './private/util'; @@ -46,9 +47,16 @@ export abstract class CloudFormationTemplate { */ export interface CloudFormationTemplateConfig { /** - * The http url of the template in S3. - */ + * The http url of the template in S3. + */ readonly httpUrl: string; + + /** + * The S3 bucket containing product stack assets. + * @default - None - no assets are used in this product + */ + readonly assetBucket?: IBucket; + } /** @@ -108,6 +116,7 @@ class CloudFormationProductStackTemplate extends CloudFormationTemplate { public bind(_scope: Construct): CloudFormationTemplateConfig { return { httpUrl: this.productStack._getTemplateUrl(), + assetBucket: this.productStack._getAssetBucket(), }; } } diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts index 91167f2454695..725d2fb8936b4 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/portfolio.ts @@ -1,7 +1,8 @@ import * as iam from '@aws-cdk/aws-iam'; +import { IBucket } from '@aws-cdk/aws-s3'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; -import { Construct } from 'constructs'; +import { Construct, IConstruct } from 'constructs'; import { MessageLanguage } from './common'; import { CloudFormationRuleConstraintOptions, CommonConstraintOptions, @@ -105,7 +106,7 @@ export interface IPortfolio extends cdk.IResource { * @param product A service catalog product. * @param options options for the constraint. */ - constrainCloudFormationParameters(product:IProduct, options: CloudFormationRuleConstraintOptions): void; + constrainCloudFormationParameters(product: IProduct, options: CloudFormationRuleConstraintOptions): void; /** * Force users to assume a certain role when launching a product. @@ -155,6 +156,8 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { public abstract readonly portfolioArn: string; public abstract readonly portfolioId: string; private readonly associatedPrincipals: Set = new Set(); + private readonly assetBuckets: Set = new Set(); + private readonly sharedAccounts: string[] = []; public giveAccessToRole(role: iam.IRole): void { this.associatePrincipal(role.roleArn, role.node.addr); @@ -169,11 +172,17 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { } public addProduct(product: IProduct): void { + if (product.assetBuckets) { + for (const bucket of product.assetBuckets) { + this.assetBuckets.add(bucket); + } + } AssociationManager.associateProductWithPortfolio(this, product, undefined); } public shareWithAccount(accountId: string, options: PortfolioShareOptions = {}): void { const hashId = this.generateUniqueHash(accountId); + this.sharedAccounts.push(accountId); new CfnPortfolioShare(this, `PortfolioShare${hashId}`, { portfolioId: this.portfolioId, accountId: accountId, @@ -236,6 +245,19 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { } } + /** + * Gives access to Asset Buckets to Shared Accounts. + * + */ + protected addBucketPermissionsToSharedAccounts() { + if (this.sharedAccounts.length > 0) { + for (const bucket of this.assetBuckets) { + bucket.grantRead(new iam.CompositePrincipal(...this.sharedAccounts.map(account => new iam.AccountPrincipal(account))), + ); + } + } + } + /** * Create a unique id based off the L1 CfnPortfolio or the arn of an imported portfolio. */ @@ -336,6 +358,15 @@ export class Portfolio extends PortfolioBase { if (props.tagOptions !== undefined) { this.associateTagOptions(props.tagOptions); } + + const portfolioNodeId = this.node.id; + cdk.Aspects.of(this).add({ + visit(c: IConstruct) { + if (c.node.id === portfolioNodeId) { + (c as Portfolio).addBucketPermissionsToSharedAccounts(); + }; + }, + }); } protected generateUniqueHash(value: string): string { diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-synthesizer.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-synthesizer.ts index 73f6e740d1790..55cb511b65424 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-synthesizer.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-synthesizer.ts @@ -1,4 +1,7 @@ +import { CfnBucket, IBucket } from '@aws-cdk/aws-s3'; +import { BucketDeployment, Source } from '@aws-cdk/aws-s3-deployment'; import * as cdk from '@aws-cdk/core'; +import { ProductStack } from '../product-stack'; /** * Deployment environment for an AWS Service Catalog product stack. @@ -6,8 +9,58 @@ import * as cdk from '@aws-cdk/core'; * Interoperates with the StackSynthesizer of the parent stack. */ export class ProductStackSynthesizer extends cdk.StackSynthesizer { - public addFileAsset(_asset: cdk.FileAssetSource): cdk.FileAssetLocation { - throw new Error('Service Catalog Product Stacks cannot use Assets'); + private readonly assetBucket?: IBucket; + private bucketDeployment?: BucketDeployment; + + constructor(assetBucket?: IBucket) { + super(); + this.assetBucket = assetBucket; + } + + public addFileAsset(asset: cdk.FileAssetSource): cdk.FileAssetLocation { + if (!this.assetBucket) { + throw new Error('An Asset Bucket must be provided to use Assets'); + } + const outdir = cdk.App.of(this.boundStack)?.outdir ?? 'cdk.out'; + const assetPath = `./${outdir}/${asset.fileName}`; + if (!this.bucketDeployment) { + const parentStack = (this.boundStack as ProductStack)._getParentStack(); + if (!cdk.Resource.isOwnedResource(this.assetBucket)) { + cdk.Annotations.of(parentStack).addWarning('[WARNING] Bucket Policy Permissions cannot be added to' + + ' referenced Bucket. Please make sure your bucket has the correct permissions'); + } + this.bucketDeployment = new BucketDeployment(parentStack, 'AssetsBucketDeployment', { + sources: [Source.asset(assetPath)], + destinationBucket: this.assetBucket, + extract: false, + prune: false, + }); + } else { + this.bucketDeployment.addSource(Source.asset(assetPath)); + } + + const physicalName = this.physicalNameOfBucket(this.assetBucket); + + const bucketName = physicalName; + const s3Filename = asset.fileName?.split('.')[1] + '.zip'; + const objectKey = `${s3Filename}`; + const s3ObjectUrl = `s3://${bucketName}/${objectKey}`; + const httpUrl = `https://s3.${bucketName}/${objectKey}`; + + return { bucketName, objectKey, httpUrl, s3ObjectUrl, s3Url: httpUrl }; + } + + private physicalNameOfBucket(bucket: IBucket) { + let resolvedName; + if (cdk.Resource.isOwnedResource(bucket)) { + resolvedName = cdk.Stack.of(bucket).resolve((bucket.node.defaultChild as CfnBucket).bucketName); + } else { + resolvedName = bucket.bucketName; + } + if (resolvedName === undefined) { + throw new Error('A bucketName must be provided to use Assets'); + } + return resolvedName; } public addDockerImageAsset(_asset: cdk.DockerImageAssetSource): cdk.DockerImageAssetLocation { diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts b/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts index 216b0b90b1d12..4de5fa6e02ac1 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts @@ -1,11 +1,23 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; +import { IBucket } from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { Construct } from 'constructs'; import { ProductStackSynthesizer } from './private/product-stack-synthesizer'; import { ProductStackHistory } from './product-stack-history'; +/** + * Product stack props. + */ +export interface ProductStackProps { + /** + * A Bucket can be passed to store assets, enabling ProductStack Asset support + * @default No Bucket provided and Assets will not be supported. + */ + readonly assetBucket?: IBucket; +} + /** * A Service Catalog product stack, which is similar in form to a Cloudformation nested stack. * You can add the resources to this stack that you want to define for your service catalog product. @@ -21,15 +33,19 @@ export class ProductStack extends cdk.Stack { private _templateUrl?: string; private _parentStack: cdk.Stack; - constructor(scope: Construct, id: string) { + private assetBucket?: IBucket; + + constructor(scope: Construct, id: string, props: ProductStackProps = {}) { super(scope, id, { - synthesizer: new ProductStackSynthesizer(), + synthesizer: new ProductStackSynthesizer(props.assetBucket), }); this._parentStack = findParentStack(scope); // this is the file name of the synthesized template file within the cloud assembly this.templateFile = `${cdk.Names.uniqueId(this)}.product.template.json`; + + this.assetBucket = props.assetBucket; } /** @@ -50,6 +66,24 @@ export class ProductStack extends cdk.Stack { return cdk.Lazy.uncachedString({ produce: () => this._templateUrl }); } + /** + * Fetch the asset bucket. + * + * @internal + */ + public _getAssetBucket(): IBucket | undefined { + return this.assetBucket; + } + + /** + * Fetch the parent Stack. + * + * @internal + */ + public _getParentStack(): cdk.Stack { + return this._parentStack; + } + /** * Synthesize the product stack template, overrides the `super` class method. * diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/product.ts b/packages/@aws-cdk/aws-servicecatalog/lib/product.ts index 3c4e8bd9fb59f..1a71c201762e8 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/product.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/product.ts @@ -1,3 +1,4 @@ +import { IBucket } from '@aws-cdk/aws-s3'; import { ArnFormat, IResource, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CloudFormationTemplate } from './cloudformation-template'; @@ -23,6 +24,13 @@ export interface IProduct extends IResource { */ readonly productId: string; + /** + * The asset buckets of a product created via product stack. + * @attribute + * @default - Empty - no assets are used in this product + */ + readonly assetBuckets?: IBucket[]; + /** * Associate Tag Options. * A TagOption is a key-value pair managed in AWS Service Catalog. @@ -171,6 +179,11 @@ export abstract class Product extends ProductBase { export class CloudFormationProduct extends Product { public readonly productArn: string; public readonly productId: string; + /** + * The asset bucket of a product created via product stack. + * @default - Empty - no assets are used in this product + */ + public readonly assetBuckets: IBucket[] = []; constructor(scope: Construct, id: string, props: CloudFormationProductProps) { super(scope, id); @@ -206,6 +219,9 @@ export class CloudFormationProduct extends Product { props: CloudFormationProductProps): CfnCloudFormationProduct.ProvisioningArtifactPropertiesProperty[] { return props.productVersions.map(productVersion => { const template = productVersion.cloudFormationTemplate.bind(this); + if (template.assetBucket) { + this.assetBuckets.push(template.assetBucket); + } InputValidator.validateUrl(this.node.path, 'provisioning template url', template.httpUrl); return { name: productVersion.productVersionName, diff --git a/packages/@aws-cdk/aws-servicecatalog/package.json b/packages/@aws-cdk/aws-servicecatalog/package.json index 9d89427ca8684..10aedf006399f 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -85,11 +85,15 @@ "@aws-cdk/integ-runner": "0.0.0", "@aws-cdk/cfn2ts": "0.0.0", "@aws-cdk/pkglint": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", + "@aws-cdk/aws-lambda": "0.0.0", "@types/jest": "^27.5.2" }, "dependencies": { "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/aws-s3-deployment": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^10.0.0" @@ -97,7 +101,9 @@ "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/aws-iam": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/aws-s3-assets": "0.0.0", + "@aws-cdk/aws-s3-deployment": "0.0.0", "@aws-cdk/aws-sns": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^10.0.0" diff --git a/packages/@aws-cdk/aws-servicecatalog/test/assets/index.py b/packages/@aws-cdk/aws-servicecatalog/test/assets/index.py new file mode 100644 index 0000000000000..105bdadc3c1a8 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/assets/index.py @@ -0,0 +1,7 @@ +import json + +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps('Hello from Lambda!') + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/assetsv2/index.py b/packages/@aws-cdk/aws-servicecatalog/test/assetsv2/index.py new file mode 100644 index 0000000000000..059c625350f79 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/assetsv2/index.py @@ -0,0 +1,7 @@ +import json + +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps('Hello from Lambda again!') + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/cdk.out/asset.95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df.zip b/packages/@aws-cdk/aws-servicecatalog/test/cdk.out/asset.95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df.zip new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/integ-servicecatalog-portfolio.assets.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/integ-servicecatalog-portfolio.assets.json index 521c5499be89a..f9d80b2ebe145 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/integ-servicecatalog-portfolio.assets.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/integ-servicecatalog-portfolio.assets.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "files": { "eba49fd9c16122ad8d201bdd34787f3236bf3777e89affb03da8aa3f73693d79": { "source": { diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/integ.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/integ.json index c5c785c3c0b32..bedfe38a00ef7 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "testCases": { "integ.portfolio": { "stacks": [ diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/manifest.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/manifest.json index 9bc7fd1651d64..3b6afe97209a6 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "20.0.0", + "version": "21.0.0", "artifacts": { "Tree": { "type": "cdk:tree", diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/tree.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/tree.json index 23e9689b4c807..21b7fd4c57601 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.portfolio.js.snapshot/tree.json @@ -9,7 +9,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.140" } }, "integ-servicecatalog-portfolio": { @@ -730,14 +730,14 @@ } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.2bc265c5e0569aeb24a6349c15bd54e76e845892376515e036627ab0cc70bb64/index.py b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.2bc265c5e0569aeb24a6349c15bd54e76e845892376515e036627ab0cc70bb64/index.py new file mode 100644 index 0000000000000..e013fae72f87d --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.2bc265c5e0569aeb24a6349c15bd54e76e845892376515e036627ab0cc70bb64/index.py @@ -0,0 +1,308 @@ +import contextlib +import json +import logging +import os +import shutil +import subprocess +import tempfile +from urllib.request import Request, urlopen +from uuid import uuid4 +from zipfile import ZipFile + +import boto3 + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +cloudfront = boto3.client('cloudfront') +s3 = boto3.client('s3') + +CFN_SUCCESS = "SUCCESS" +CFN_FAILED = "FAILED" +ENV_KEY_MOUNT_PATH = "MOUNT_PATH" +ENV_KEY_SKIP_CLEANUP = "SKIP_CLEANUP" + +CUSTOM_RESOURCE_OWNER_TAG = "aws-cdk:cr-owned" + +def handler(event, context): + + def cfn_error(message=None): + logger.error("| cfn_error: %s" % message) + cfn_send(event, context, CFN_FAILED, reason=message) + + try: + # We are not logging ResponseURL as this is a pre-signed S3 URL, and could be used to tamper + # with the response CloudFormation sees from this Custom Resource execution. + logger.info({ key:value for (key, value) in event.items() if key != 'ResponseURL'}) + + # cloudformation request type (create/update/delete) + request_type = event['RequestType'] + + # extract resource properties + props = event['ResourceProperties'] + old_props = event.get('OldResourceProperties', {}) + physical_id = event.get('PhysicalResourceId', None) + + try: + source_bucket_names = props['SourceBucketNames'] + source_object_keys = props['SourceObjectKeys'] + source_markers = props.get('SourceMarkers', None) + dest_bucket_name = props['DestinationBucketName'] + dest_bucket_prefix = props.get('DestinationBucketKeyPrefix', '') + extract = props.get('Extract', 'true') == 'true' + retain_on_delete = props.get('RetainOnDelete', "true") == "true" + distribution_id = props.get('DistributionId', '') + user_metadata = props.get('UserMetadata', {}) + system_metadata = props.get('SystemMetadata', {}) + prune = props.get('Prune', 'true').lower() == 'true' + exclude = props.get('Exclude', []) + include = props.get('Include', []) + + # backwards compatibility - if "SourceMarkers" is not specified, + # assume all sources have an empty market map + if source_markers is None: + source_markers = [{} for i in range(len(source_bucket_names))] + + default_distribution_path = dest_bucket_prefix + if not default_distribution_path.endswith("/"): + default_distribution_path += "/" + if not default_distribution_path.startswith("/"): + default_distribution_path = "/" + default_distribution_path + default_distribution_path += "*" + + distribution_paths = props.get('DistributionPaths', [default_distribution_path]) + except KeyError as e: + cfn_error("missing request resource property %s. props: %s" % (str(e), props)) + return + + # treat "/" as if no prefix was specified + if dest_bucket_prefix == "/": + dest_bucket_prefix = "" + + s3_source_zips = list(map(lambda name, key: "s3://%s/%s" % (name, key), source_bucket_names, source_object_keys)) + s3_dest = "s3://%s/%s" % (dest_bucket_name, dest_bucket_prefix) + old_s3_dest = "s3://%s/%s" % (old_props.get("DestinationBucketName", ""), old_props.get("DestinationBucketKeyPrefix", "")) + + + # obviously this is not + if old_s3_dest == "s3:///": + old_s3_dest = None + + logger.info("| s3_dest: %s" % s3_dest) + logger.info("| old_s3_dest: %s" % old_s3_dest) + + # if we are creating a new resource, allocate a physical id for it + # otherwise, we expect physical id to be relayed by cloudformation + if request_type == "Create": + physical_id = "aws.cdk.s3deployment.%s" % str(uuid4()) + else: + if not physical_id: + cfn_error("invalid request: request type is '%s' but 'PhysicalResourceId' is not defined" % request_type) + return + + # delete or create/update (only if "retain_on_delete" is false) + if request_type == "Delete" and not retain_on_delete: + if not bucket_owned(dest_bucket_name, dest_bucket_prefix): + aws_command("s3", "rm", s3_dest, "--recursive") + + # if we are updating without retention and the destination changed, delete first + if request_type == "Update" and not retain_on_delete and old_s3_dest != s3_dest: + if not old_s3_dest: + logger.warn("cannot delete old resource without old resource properties") + return + + aws_command("s3", "rm", old_s3_dest, "--recursive") + + if request_type == "Update" or request_type == "Create": + s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, extract) + + if distribution_id: + cloudfront_invalidate(distribution_id, distribution_paths) + + cfn_send(event, context, CFN_SUCCESS, physicalResourceId=physical_id, responseData={ + # Passing through the ARN sequences dependencees on the deployment + 'DestinationBucketArn': props.get('DestinationBucketArn'), + 'SourceObjectKeys': props.get('SourceObjectKeys'), + }) + except KeyError as e: + cfn_error("invalid request. Missing key %s" % str(e)) + except Exception as e: + logger.exception(e) + cfn_error(str(e)) + +#--------------------------------------------------------------------------------------------------- +# populate all files from s3_source_zips to a destination bucket +def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, extract): + # list lengths are equal + if len(s3_source_zips) != len(source_markers): + raise Exception("'source_markers' and 's3_source_zips' must be the same length") + + # create a temporary working directory in /tmp or if enabled an attached efs volume + if ENV_KEY_MOUNT_PATH in os.environ: + workdir = os.getenv(ENV_KEY_MOUNT_PATH) + "/" + str(uuid4()) + os.mkdir(workdir) + else: + workdir = tempfile.mkdtemp() + + logger.info("| workdir: %s" % workdir) + + # create a directory into which we extract the contents of the zip file + contents_dir=os.path.join(workdir, 'contents') + os.mkdir(contents_dir) + + try: + # download the archive from the source and extract to "contents" + for i in range(len(s3_source_zips)): + s3_source_zip = s3_source_zips[i] + markers = source_markers[i] + + if extract: + archive=os.path.join(workdir, str(uuid4())) + logger.info("archive: %s" % archive) + aws_command("s3", "cp", s3_source_zip, archive) + logger.info("| extracting archive to: %s\n" % contents_dir) + logger.info("| markers: %s" % markers) + extract_and_replace_markers(archive, contents_dir, markers) + else: + logger.info("| copying archive to: %s\n" % contents_dir) + aws_command("s3", "cp", s3_source_zip, contents_dir) + + # sync from "contents" to destination + + s3_command = ["s3", "sync"] + + if prune: + s3_command.append("--delete") + + if exclude: + for filter in exclude: + s3_command.extend(["--exclude", filter]) + + if include: + for filter in include: + s3_command.extend(["--include", filter]) + + s3_command.extend([contents_dir, s3_dest]) + s3_command.extend(create_metadata_args(user_metadata, system_metadata)) + aws_command(*s3_command) + finally: + if not os.getenv(ENV_KEY_SKIP_CLEANUP): + shutil.rmtree(workdir) + +#--------------------------------------------------------------------------------------------------- +# invalidate files in the CloudFront distribution edge caches +def cloudfront_invalidate(distribution_id, distribution_paths): + invalidation_resp = cloudfront.create_invalidation( + DistributionId=distribution_id, + InvalidationBatch={ + 'Paths': { + 'Quantity': len(distribution_paths), + 'Items': distribution_paths + }, + 'CallerReference': str(uuid4()), + }) + # by default, will wait up to 10 minutes + cloudfront.get_waiter('invalidation_completed').wait( + DistributionId=distribution_id, + Id=invalidation_resp['Invalidation']['Id']) + +#--------------------------------------------------------------------------------------------------- +# set metadata +def create_metadata_args(raw_user_metadata, raw_system_metadata): + if len(raw_user_metadata) == 0 and len(raw_system_metadata) == 0: + return [] + + format_system_metadata_key = lambda k: k.lower() + format_user_metadata_key = lambda k: k.lower() + + system_metadata = { format_system_metadata_key(k): v for k, v in raw_system_metadata.items() } + user_metadata = { format_user_metadata_key(k): v for k, v in raw_user_metadata.items() } + + flatten = lambda l: [item for sublist in l for item in sublist] + system_args = flatten([[f"--{k}", v] for k, v in system_metadata.items()]) + user_args = ["--metadata", json.dumps(user_metadata, separators=(',', ':'))] if len(user_metadata) > 0 else [] + + return system_args + user_args + ["--metadata-directive", "REPLACE"] + +#--------------------------------------------------------------------------------------------------- +# executes an "aws" cli command +def aws_command(*args): + aws="/opt/awscli/aws" # from AwsCliLayer + logger.info("| aws %s" % ' '.join(args)) + subprocess.check_call([aws] + list(args)) + +#--------------------------------------------------------------------------------------------------- +# sends a response to cloudformation +def cfn_send(event, context, responseStatus, responseData={}, physicalResourceId=None, noEcho=False, reason=None): + + responseUrl = event['ResponseURL'] + + responseBody = {} + responseBody['Status'] = responseStatus + responseBody['Reason'] = reason or ('See the details in CloudWatch Log Stream: ' + context.log_stream_name) + responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name + responseBody['StackId'] = event['StackId'] + responseBody['RequestId'] = event['RequestId'] + responseBody['LogicalResourceId'] = event['LogicalResourceId'] + responseBody['NoEcho'] = noEcho + responseBody['Data'] = responseData + + body = json.dumps(responseBody) + logger.info("| response body:\n" + body) + + headers = { + 'content-type' : '', + 'content-length' : str(len(body)) + } + + try: + request = Request(responseUrl, method='PUT', data=bytes(body.encode('utf-8')), headers=headers) + with contextlib.closing(urlopen(request)) as response: + logger.info("| status code: " + response.reason) + except Exception as e: + logger.error("| unable to send response to CloudFormation") + logger.exception(e) + + +#--------------------------------------------------------------------------------------------------- +# check if bucket is owned by a custom resource +# if it is then we don't want to delete content +def bucket_owned(bucketName, keyPrefix): + tag = CUSTOM_RESOURCE_OWNER_TAG + if keyPrefix != "": + tag = tag + ':' + keyPrefix + try: + request = s3.get_bucket_tagging( + Bucket=bucketName, + ) + return any((x["Key"].startswith(tag)) for x in request["TagSet"]) + except Exception as e: + logger.info("| error getting tags from bucket") + logger.exception(e) + return False + +# extract archive and replace markers in output files +def extract_and_replace_markers(archive, contents_dir, markers): + with ZipFile(archive, "r") as zip: + zip.extractall(contents_dir) + + # replace markers for this source + for file in zip.namelist(): + file_path = os.path.join(contents_dir, file) + if os.path.isdir(file_path): continue + replace_markers(file_path, markers) + +def replace_markers(filename, markers): + # convert the dict of string markers to binary markers + replace_tokens = dict([(k.encode('utf-8'), v.encode('utf-8')) for k, v in markers.items()]) + + outfile = filename + '.new' + with open(filename, 'rb') as fi, open(outfile, 'wb') as fo: + for line in fi: + for token in replace_tokens: + line = line.replace(token, replace_tokens[token]) + fo.write(line) + + # # delete the original file and rename the new one to the original + os.remove(filename) + os.rename(outfile, filename) diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c/__entrypoint__.js b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c/__entrypoint__.js new file mode 100755 index 0000000000000..1e3a3093c1706 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c/__entrypoint__.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFNLE1BQU0sR0FBRyxNQUFNLFdBQVcsQ0FBQyxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFMUQsdURBQXVEO1FBQ3ZELE1BQU0sYUFBYSxHQUFHLGNBQWMsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFcEQsMkJBQTJCO1FBQzNCLE1BQU0sY0FBYyxDQUFDLFNBQVMsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUNoRDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1YsTUFBTSxJQUFJLEdBQWE7WUFDckIsR0FBRyxLQUFLO1lBQ1IsTUFBTSxFQUFFLGdCQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxPQUFPO1NBQzFELENBQUM7UUFFRixJQUFJLENBQUMsSUFBSSxDQUFDLGtCQUFrQixFQUFFO1lBQzVCLHlFQUF5RTtZQUN6RSxtRUFBbUU7WUFDbkUsd0VBQXdFO1lBQ3hFLHFFQUFxRTtZQUNyRSxnQ0FBZ0M7WUFDaEMsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsRUFBRTtnQkFDbEMsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsNEdBQTRHLENBQUMsQ0FBQztnQkFDM0gsSUFBSSxDQUFDLGtCQUFrQixHQUFHLGdDQUFnQyxDQUFDO2FBQzVEO2lCQUFNO2dCQUNMLGtFQUFrRTtnQkFDbEUsNkRBQTZEO2dCQUM3RCxnQkFBUSxDQUFDLEdBQUcsQ0FBQyw2REFBNkQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7YUFDcEc7U0FDRjtRQUVELG1FQUFtRTtRQUNuRSxNQUFNLGNBQWMsQ0FBQyxRQUFRLEVBQUUsSUFBSSxDQUFDLENBQUM7S0FDdEM7QUFDSCxDQUFDO0FBbkRELDBCQW1EQztBQUVELFNBQVMsY0FBYyxDQUNyQixVQUF5RixFQUN6RixrQkFBMEMsRUFBRztJQUU3QyxzRUFBc0U7SUFDdEUsdUJBQXVCO0lBQ3ZCLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDLGtCQUFrQixJQUFJLFVBQVUsQ0FBQyxrQkFBa0IsSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDO0lBRXZILGtFQUFrRTtJQUNsRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLEtBQUssUUFBUSxJQUFJLGtCQUFrQixLQUFLLFVBQVUsQ0FBQyxrQkFBa0IsRUFBRTtRQUMvRixNQUFNLElBQUksS0FBSyxDQUFDLHdEQUF3RCxVQUFVLENBQUMsa0JBQWtCLFNBQVMsZUFBZSxDQUFDLGtCQUFrQixtQkFBbUIsQ0FBQyxDQUFDO0tBQ3RLO0lBRUQsMERBQTBEO0lBQzFELE9BQU87UUFDTCxHQUFHLFVBQVU7UUFDYixHQUFHLGVBQWU7UUFDbEIsa0JBQWtCLEVBQUUsa0JBQWtCO0tBQ3ZDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxNQUE0QixFQUFFLEtBQWU7SUFDekUsTUFBTSxJQUFJLEdBQW1EO1FBQzNELE1BQU0sRUFBRSxNQUFNO1FBQ2QsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksTUFBTTtRQUM5QixPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU87UUFDdEIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzFCLGtCQUFrQixFQUFFLEtBQUssQ0FBQyxrQkFBa0IsSUFBSSwwQkFBMEI7UUFDMUUsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtRQUMxQyxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07UUFDcEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO0tBQ2pCLENBQUM7SUFFRixnQkFBUSxDQUFDLEdBQUcsQ0FBQyxtQ0FBbUMsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV4RCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQy9DLE1BQU0sR0FBRyxHQUFHO1FBQ1YsUUFBUSxFQUFFLFNBQVMsQ0FBQyxRQUFRO1FBQzVCLElBQUksRUFBRSxTQUFTLENBQUMsSUFBSTtRQUNwQixNQUFNLEVBQUUsS0FBSztRQUNiLE9BQU8sRUFBRSxFQUFFLGNBQWMsRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRTtLQUN2RSxDQUFDO0lBRUYsTUFBTSxZQUFZLEdBQUc7UUFDbkIsUUFBUSxFQUFFLENBQUM7UUFDWCxLQUFLLEVBQUUsSUFBSTtLQUNaLENBQUM7SUFDRixNQUFNLFdBQVcsQ0FBQyxZQUFZLEVBQUUsZ0JBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDL0UsQ0FBQztBQUVELEtBQUssVUFBVSxzQkFBc0IsQ0FBQyxPQUE2QixFQUFFLFlBQW9CO0lBQ3ZGLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSTtZQUNGLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUN2RCxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDO1lBQzVCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQztTQUNmO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDWDtJQUNILENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLEdBQVcsRUFBRSxHQUFHLE1BQWE7SUFDL0Msc0NBQXNDO0lBQ3RDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUM7QUFDOUIsQ0FBQztBQVNELFNBQWdCLFdBQVcsQ0FBMEIsT0FBcUIsRUFBRSxFQUE0QjtJQUN0RyxPQUFPLEtBQUssRUFBRSxHQUFHLEVBQUssRUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCByZXNwOiBSZXNwb25zZSA9IHtcbiAgICAgIC4uLmV2ZW50LFxuICAgICAgUmVhc29uOiBleHRlcm5hbC5pbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgIH07XG5cbiAgICBpZiAoIXJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAvLyBzcGVjaWFsIGNhc2U6IGlmIENSRUFURSBmYWlscywgd2hpY2ggdXN1YWxseSBpbXBsaWVzLCB3ZSB1c3VhbGx5IGRvbid0XG4gICAgICAvLyBoYXZlIGEgcGh5c2ljYWwgcmVzb3VyY2UgaWQuIGluIHRoaXMgY2FzZSwgdGhlIHN1YnNlcXVlbnQgREVMRVRFXG4gICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgIC8vIGFkZHJlc3MgdGhpcywgd2UgdXNlIGEgbWFya2VyIHNvIHRoZSBwcm92aWRlciBmcmFtZXdvcmsgY2FuIHNpbXBseVxuICAgICAgLy8gaWdub3JlIHRoZSBzdWJzZXF1ZW50IERFTEVURS5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgZXh0ZXJuYWwubG9nKCdDUkVBVEUgZmFpbGVkLCByZXNwb25kaW5nIHdpdGggYSBtYXJrZXIgcGh5c2ljYWwgcmVzb3VyY2UgaWQgc28gdGhhdCB0aGUgc3Vic2VxdWVudCBERUxFVEUgd2lsbCBiZSBpZ25vcmVkJyk7XG4gICAgICAgIHJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkID0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVI7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgLy8gdGVycmlibHkgd3JvbmcgYmVjYXVzZSBhbGwgb3RoZXIgZXZlbnRzIHNob3VsZCBoYXZlIGFuIElELlxuICAgICAgICBleHRlcm5hbC5sb2coYEVSUk9SOiBNYWxmb3JtZWQgZXZlbnQuIFwiUGh5c2ljYWxSZXNvdXJjZUlkXCIgaXMgcmVxdWlyZWQ6ICR7SlNPTi5zdHJpbmdpZnkoZXZlbnQpfWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHRoaXMgaXMgYW4gYWN0dWFsIGVycm9yLCBmYWlsIHRoZSBhY3Rpdml0eSBhbHRvZ2V0aGVyIGFuZCBleGlzdC5cbiAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgcmVzcCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyUmVzcG9uc2UoXG4gIGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiB7IFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZyB9LFxuICBoYW5kbGVyUmVzcG9uc2U6IHZvaWQgfCBIYW5kbGVyUmVzcG9uc2UgPSB7IH0pOiBSZXNwb25zZSB7XG5cbiAgLy8gaWYgcGh5c2ljYWwgSUQgaXMgbm90IHJldHVybmVkLCB3ZSBoYXZlIHNvbWUgZGVmYXVsdHMgZm9yIHlvdSBiYXNlZFxuICAvLyBvbiB0aGUgcmVxdWVzdCB0eXBlLlxuICBjb25zdCBwaHlzaWNhbFJlc291cmNlSWQgPSBoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUmVxdWVzdElkO1xuXG4gIC8vIGlmIHdlIGFyZSBpbiBERUxFVEUgYW5kIHBoeXNpY2FsIElEIHdhcyBjaGFuZ2VkLCBpdCdzIGFuIGVycm9yLlxuICBpZiAoY2ZuUmVxdWVzdC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScgJiYgcGh5c2ljYWxSZXNvdXJjZUlkICE9PSBjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgREVMRVRFOiBjYW5ub3QgY2hhbmdlIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBmcm9tIFwiJHtjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZH1cIiB0byBcIiR7aGFuZGxlclJlc3BvbnNlLlBoeXNpY2FsUmVzb3VyY2VJZH1cIiBkdXJpbmcgZGVsZXRpb25gKTtcbiAgfVxuXG4gIC8vIG1lcmdlIHJlcXVlc3QgZXZlbnQgYW5kIHJlc3VsdCBldmVudCAocmVzdWx0IHByZXZhaWxzKS5cbiAgcmV0dXJuIHtcbiAgICAuLi5jZm5SZXF1ZXN0LFxuICAgIC4uLmhhbmRsZXJSZXNwb25zZSxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3VibWl0UmVzcG9uc2Uoc3RhdHVzOiAnU1VDQ0VTUycgfCAnRkFJTEVEJywgZXZlbnQ6IFJlc3BvbnNlKSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBldmVudC5SZWFzb24gPz8gc3RhdHVzLFxuICAgIFN0YWNrSWQ6IGV2ZW50LlN0YWNrSWQsXG4gICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgfHwgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIsXG4gICAgTG9naWNhbFJlc291cmNlSWQ6IGV2ZW50LkxvZ2ljYWxSZXNvdXJjZUlkLFxuICAgIE5vRWNobzogZXZlbnQuTm9FY2hvLFxuICAgIERhdGE6IGV2ZW50LkRhdGEsXG4gIH07XG5cbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBqc29uKTtcblxuICBjb25zdCByZXNwb25zZUJvZHkgPSBKU09OLnN0cmluZ2lmeShqc29uKTtcbiAgY29uc3QgcGFyc2VkVXJsID0gdXJsLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgY29uc3QgcmVxID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c/index.js b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c/index.js new file mode 100755 index 0000000000000..7ce4156d4ba41 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c/index.js @@ -0,0 +1,78 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +// eslint-disable-next-line import/no-extraneous-dependencies +const aws_sdk_1 = require("aws-sdk"); +const AUTO_DELETE_OBJECTS_TAG = 'aws-cdk:auto-delete-objects'; +const s3 = new aws_sdk_1.S3(); +async function handler(event) { + switch (event.RequestType) { + case 'Create': + return; + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.BucketName); + } +} +exports.handler = handler; +async function onUpdate(event) { + const updateEvent = event; + const oldBucketName = updateEvent.OldResourceProperties?.BucketName; + const newBucketName = updateEvent.ResourceProperties?.BucketName; + const bucketNameHasChanged = newBucketName != null && oldBucketName != null && newBucketName !== oldBucketName; + /* If the name of the bucket has changed, CloudFormation will try to delete the bucket + and create a new one with the new name. So we have to delete the contents of the + bucket so that this operation does not fail. */ + if (bucketNameHasChanged) { + return onDelete(oldBucketName); + } +} +/** + * Recursively delete all items in the bucket + * + * @param bucketName the bucket name + */ +async function emptyBucket(bucketName) { + const listedObjects = await s3.listObjectVersions({ Bucket: bucketName }).promise(); + const contents = [...listedObjects.Versions ?? [], ...listedObjects.DeleteMarkers ?? []]; + if (contents.length === 0) { + return; + } + const records = contents.map((record) => ({ Key: record.Key, VersionId: record.VersionId })); + await s3.deleteObjects({ Bucket: bucketName, Delete: { Objects: records } }).promise(); + if (listedObjects?.IsTruncated) { + await emptyBucket(bucketName); + } +} +async function onDelete(bucketName) { + if (!bucketName) { + throw new Error('No BucketName was provided.'); + } + if (!await isBucketTaggedForDeletion(bucketName)) { + process.stdout.write(`Bucket does not have '${AUTO_DELETE_OBJECTS_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyBucket(bucketName); + } + catch (e) { + if (e.code !== 'NoSuchBucket') { + throw e; + } + // Bucket doesn't exist. Ignoring + } +} +/** + * The bucket will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is every deleted before the bucket, it must be because + * `autoDeleteObjects` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isBucketTaggedForDeletion(bucketName) { + const response = await s3.getBucketTagging({ Bucket: bucketName }).promise(); + return response.TagSet.some(tag => tag.Key === AUTO_DELETE_OBJECTS_TAG && tag.Value === 'true'); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2REFBNkQ7QUFDN0QscUNBQTZCO0FBRTdCLE1BQU0sdUJBQXVCLEdBQUcsNkJBQTZCLENBQUM7QUFFOUQsTUFBTSxFQUFFLEdBQUcsSUFBSSxZQUFFLEVBQUUsQ0FBQztBQUViLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0Q7SUFDOUUsUUFBUSxLQUFLLENBQUMsV0FBVyxFQUFFO1FBQ3pCLEtBQUssUUFBUTtZQUNYLE9BQU87UUFDVCxLQUFLLFFBQVE7WUFDWCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN6QixLQUFLLFFBQVE7WUFDWCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsVUFBVSxDQUFDLENBQUM7S0FDekQ7QUFDSCxDQUFDO0FBVEQsMEJBU0M7QUFFRCxLQUFLLFVBQVUsUUFBUSxDQUFDLEtBQWtEO0lBQ3hFLE1BQU0sV0FBVyxHQUFHLEtBQTBELENBQUM7SUFDL0UsTUFBTSxhQUFhLEdBQUcsV0FBVyxDQUFDLHFCQUFxQixFQUFFLFVBQVUsQ0FBQztJQUNwRSxNQUFNLGFBQWEsR0FBRyxXQUFXLENBQUMsa0JBQWtCLEVBQUUsVUFBVSxDQUFDO0lBQ2pFLE1BQU0sb0JBQW9CLEdBQUcsYUFBYSxJQUFJLElBQUksSUFBSSxhQUFhLElBQUksSUFBSSxJQUFJLGFBQWEsS0FBSyxhQUFhLENBQUM7SUFFL0c7O3NEQUVrRDtJQUNsRCxJQUFJLG9CQUFvQixFQUFFO1FBQ3hCLE9BQU8sUUFBUSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0tBQ2hDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxLQUFLLFVBQVUsV0FBVyxDQUFDLFVBQWtCO0lBQzNDLE1BQU0sYUFBYSxHQUFHLE1BQU0sRUFBRSxDQUFDLGtCQUFrQixDQUFDLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDcEYsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFHLGFBQWEsQ0FBQyxRQUFRLElBQUksRUFBRSxFQUFFLEdBQUcsYUFBYSxDQUFDLGFBQWEsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUN6RixJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQ3pCLE9BQU87S0FDUjtJQUVELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxNQUFXLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxHQUFHLEVBQUUsTUFBTSxDQUFDLEdBQUcsRUFBRSxTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUNsRyxNQUFNLEVBQUUsQ0FBQyxhQUFhLENBQUMsRUFBRSxNQUFNLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFFdkYsSUFBSSxhQUFhLEVBQUUsV0FBVyxFQUFFO1FBQzlCLE1BQU0sV0FBVyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0tBQy9CO0FBQ0gsQ0FBQztBQUVELEtBQUssVUFBVSxRQUFRLENBQUMsVUFBbUI7SUFDekMsSUFBSSxDQUFDLFVBQVUsRUFBRTtRQUNmLE1BQU0sSUFBSSxLQUFLLENBQUMsNkJBQTZCLENBQUMsQ0FBQztLQUNoRDtJQUNELElBQUksQ0FBQyxNQUFNLHlCQUF5QixDQUFDLFVBQVUsQ0FBQyxFQUFFO1FBQ2hELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLHlCQUF5Qix1QkFBdUIsNkJBQTZCLENBQUMsQ0FBQztRQUNwRyxPQUFPO0tBQ1I7SUFDRCxJQUFJO1FBQ0YsTUFBTSxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7S0FDL0I7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyxjQUFjLEVBQUU7WUFDN0IsTUFBTSxDQUFDLENBQUM7U0FDVDtRQUNELGlDQUFpQztLQUNsQztBQUNILENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsS0FBSyxVQUFVLHlCQUF5QixDQUFDLFVBQWtCO0lBQ3pELE1BQU0sUUFBUSxHQUFHLE1BQU0sRUFBRSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDN0UsT0FBTyxRQUFRLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssdUJBQXVCLElBQUksR0FBRyxDQUFDLEtBQUssS0FBSyxNQUFNLENBQUMsQ0FBQztBQUNsRyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llc1xuaW1wb3J0IHsgUzMgfSBmcm9tICdhd3Mtc2RrJztcblxuY29uc3QgQVVUT19ERUxFVEVfT0JKRUNUU19UQUcgPSAnYXdzLWNkazphdXRvLWRlbGV0ZS1vYmplY3RzJztcblxuY29uc3QgczMgPSBuZXcgUzMoKTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQpIHtcbiAgc3dpdGNoIChldmVudC5SZXF1ZXN0VHlwZSkge1xuICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgICByZXR1cm47XG4gICAgY2FzZSAnVXBkYXRlJzpcbiAgICAgIHJldHVybiBvblVwZGF0ZShldmVudCk7XG4gICAgY2FzZSAnRGVsZXRlJzpcbiAgICAgIHJldHVybiBvbkRlbGV0ZShldmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LkJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIG9uVXBkYXRlKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGNvbnN0IHVwZGF0ZUV2ZW50ID0gZXZlbnQgYXMgQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VVcGRhdGVFdmVudDtcbiAgY29uc3Qgb2xkQnVja2V0TmFtZSA9IHVwZGF0ZUV2ZW50Lk9sZFJlc291cmNlUHJvcGVydGllcz8uQnVja2V0TmFtZTtcbiAgY29uc3QgbmV3QnVja2V0TmFtZSA9IHVwZGF0ZUV2ZW50LlJlc291cmNlUHJvcGVydGllcz8uQnVja2V0TmFtZTtcbiAgY29uc3QgYnVja2V0TmFtZUhhc0NoYW5nZWQgPSBuZXdCdWNrZXROYW1lICE9IG51bGwgJiYgb2xkQnVja2V0TmFtZSAhPSBudWxsICYmIG5ld0J1Y2tldE5hbWUgIT09IG9sZEJ1Y2tldE5hbWU7XG5cbiAgLyogSWYgdGhlIG5hbWUgb2YgdGhlIGJ1Y2tldCBoYXMgY2hhbmdlZCwgQ2xvdWRGb3JtYXRpb24gd2lsbCB0cnkgdG8gZGVsZXRlIHRoZSBidWNrZXRcbiAgICAgYW5kIGNyZWF0ZSBhIG5ldyBvbmUgd2l0aCB0aGUgbmV3IG5hbWUuIFNvIHdlIGhhdmUgdG8gZGVsZXRlIHRoZSBjb250ZW50cyBvZiB0aGVcbiAgICAgYnVja2V0IHNvIHRoYXQgdGhpcyBvcGVyYXRpb24gZG9lcyBub3QgZmFpbC4gKi9cbiAgaWYgKGJ1Y2tldE5hbWVIYXNDaGFuZ2VkKSB7XG4gICAgcmV0dXJuIG9uRGVsZXRlKG9sZEJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbi8qKlxuICogUmVjdXJzaXZlbHkgZGVsZXRlIGFsbCBpdGVtcyBpbiB0aGUgYnVja2V0XG4gKlxuICogQHBhcmFtIGJ1Y2tldE5hbWUgdGhlIGJ1Y2tldCBuYW1lXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGVtcHR5QnVja2V0KGJ1Y2tldE5hbWU6IHN0cmluZykge1xuICBjb25zdCBsaXN0ZWRPYmplY3RzID0gYXdhaXQgczMubGlzdE9iamVjdFZlcnNpb25zKHsgQnVja2V0OiBidWNrZXROYW1lIH0pLnByb21pc2UoKTtcbiAgY29uc3QgY29udGVudHMgPSBbLi4ubGlzdGVkT2JqZWN0cy5WZXJzaW9ucyA/PyBbXSwgLi4ubGlzdGVkT2JqZWN0cy5EZWxldGVNYXJrZXJzID8/IFtdXTtcbiAgaWYgKGNvbnRlbnRzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGNvbnN0IHJlY29yZHMgPSBjb250ZW50cy5tYXAoKHJlY29yZDogYW55KSA9PiAoeyBLZXk6IHJlY29yZC5LZXksIFZlcnNpb25JZDogcmVjb3JkLlZlcnNpb25JZCB9KSk7XG4gIGF3YWl0IHMzLmRlbGV0ZU9iamVjdHMoeyBCdWNrZXQ6IGJ1Y2tldE5hbWUsIERlbGV0ZTogeyBPYmplY3RzOiByZWNvcmRzIH0gfSkucHJvbWlzZSgpO1xuXG4gIGlmIChsaXN0ZWRPYmplY3RzPy5Jc1RydW5jYXRlZCkge1xuICAgIGF3YWl0IGVtcHR5QnVja2V0KGJ1Y2tldE5hbWUpO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIG9uRGVsZXRlKGJ1Y2tldE5hbWU/OiBzdHJpbmcpIHtcbiAgaWYgKCFidWNrZXROYW1lKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdObyBCdWNrZXROYW1lIHdhcyBwcm92aWRlZC4nKTtcbiAgfVxuICBpZiAoIWF3YWl0IGlzQnVja2V0VGFnZ2VkRm9yRGVsZXRpb24oYnVja2V0TmFtZSkpIHtcbiAgICBwcm9jZXNzLnN0ZG91dC53cml0ZShgQnVja2V0IGRvZXMgbm90IGhhdmUgJyR7QVVUT19ERUxFVEVfT0JKRUNUU19UQUd9JyB0YWcsIHNraXBwaW5nIGNsZWFuaW5nLlxcbmApO1xuICAgIHJldHVybjtcbiAgfVxuICB0cnkge1xuICAgIGF3YWl0IGVtcHR5QnVja2V0KGJ1Y2tldE5hbWUpO1xuICB9IGNhdGNoIChlKSB7XG4gICAgaWYgKGUuY29kZSAhPT0gJ05vU3VjaEJ1Y2tldCcpIHtcbiAgICAgIHRocm93IGU7XG4gICAgfVxuICAgIC8vIEJ1Y2tldCBkb2Vzbid0IGV4aXN0LiBJZ25vcmluZ1xuICB9XG59XG5cbi8qKlxuICogVGhlIGJ1Y2tldCB3aWxsIG9ubHkgYmUgdGFnZ2VkIGZvciBkZWxldGlvbiBpZiBpdCdzIGJlaW5nIGRlbGV0ZWQgaW4gdGhlIHNhbWVcbiAqIGRlcGxveW1lbnQgYXMgdGhpcyBDdXN0b20gUmVzb3VyY2UuXG4gKlxuICogSWYgdGhlIEN1c3RvbSBSZXNvdXJjZSBpcyBldmVyeSBkZWxldGVkIGJlZm9yZSB0aGUgYnVja2V0LCBpdCBtdXN0IGJlIGJlY2F1c2VcbiAqIGBhdXRvRGVsZXRlT2JqZWN0c2AgaGFzIGJlZW4gc3dpdGNoZWQgdG8gZmFsc2UsIGluIHdoaWNoIGNhc2UgdGhlIHRhZyB3b3VsZCBoYXZlXG4gKiBiZWVuIHJlbW92ZWQgYmVmb3JlIHdlIGdldCB0byB0aGlzIERlbGV0ZSBldmVudC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gaXNCdWNrZXRUYWdnZWRGb3JEZWxldGlvbihidWNrZXROYW1lOiBzdHJpbmcpIHtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBzMy5nZXRCdWNrZXRUYWdnaW5nKHsgQnVja2V0OiBidWNrZXROYW1lIH0pLnByb21pc2UoKTtcbiAgcmV0dXJuIHJlc3BvbnNlLlRhZ1NldC5zb21lKHRhZyA9PiB0YWcuS2V5ID09PSBBVVRPX0RFTEVURV9PQkpFQ1RTX1RBRyAmJiB0YWcuVmFsdWUgPT09ICd0cnVlJyk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510.zip b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510.zip new file mode 100644 index 0000000000000..d1065f07d934c Binary files /dev/null and b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510.zip differ diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2/index.py b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2/index.py new file mode 100644 index 0000000000000..105bdadc3c1a8 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2/index.py @@ -0,0 +1,7 @@ +import json + +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps('Hello from Lambda!') + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2/index.py b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2/index.py new file mode 100644 index 0000000000000..105bdadc3c1a8 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2/index.py @@ -0,0 +1,7 @@ +import json + +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps('Hello from Lambda!') + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d/index.py b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d/index.py new file mode 100644 index 0000000000000..059c625350f79 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d/index.py @@ -0,0 +1,7 @@ +import json + +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps('Hello from Lambda again!') + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d/index.py b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d/index.py new file mode 100644 index 0000000000000..059c625350f79 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/asset.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d/index.py @@ -0,0 +1,7 @@ +import json + +def handler(event, context): + return { + 'statusCode': 200, + 'body': json.dumps('Hello from Lambda again!') + } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/cdk.out b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/cdk.out index 588d7b269d34f..145739f539580 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"20.0.0"} \ No newline at end of file +{"version":"22.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ-servicecatalog-product.assets.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ-servicecatalog-product.assets.json index 4554ffc8470db..dc95a737f7391 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ-servicecatalog-product.assets.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ-servicecatalog-product.assets.json @@ -1,16 +1,87 @@ { - "version": "20.0.0", + "version": "22.0.0", "files": { + "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c": { + "source": { + "path": "asset.33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c", + "packaging": "zip" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c.zip", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + }, + "5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510": { + "source": { + "path": "asset.5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510.zip", + "packaging": "file" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510.zip", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + }, + "2bc265c5e0569aeb24a6349c15bd54e76e845892376515e036627ab0cc70bb64": { + "source": { + "path": "asset.2bc265c5e0569aeb24a6349c15bd54e76e845892376515e036627ab0cc70bb64", + "packaging": "zip" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "2bc265c5e0569aeb24a6349c15bd54e76e845892376515e036627ab0cc70bb64.zip", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + }, + "d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2": { + "source": { + "path": "asset.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2.d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2", + "packaging": "zip" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2.zip", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + }, + "e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d": { + "source": { + "path": "asset.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d.e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d", + "packaging": "zip" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d.zip", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + }, "b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e": { "source": { "path": "asset.b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e.json", "packaging": "file" }, "destinations": { - "current_account-current_region": { - "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", "objectKey": "b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e.json", - "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" } } }, @@ -20,36 +91,53 @@ "packaging": "file" }, "destinations": { - "current_account-current_region": { - "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", "objectKey": "6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5.json", - "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" } } }, "dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f": { "source": { - "path": "integservicecatalogproductSNSTopicProduct24C7C16DA.product.template.json", + "path": "integservicecatalogproductSNSTopicProduct3B51CF591.product.template.json", "packaging": "file" }, "destinations": { - "current_account-current_region": { - "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", "objectKey": "dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json", - "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" + } + } + }, + "c6a724b9f87d5f730d400d518f32edba51cf14c851d05c5eea07a9170b9e510a": { + "source": { + "path": "integservicecatalogproductS3AssetProductCED6E119.product.template.json", + "packaging": "file" + }, + "destinations": { + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "c6a724b9f87d5f730d400d518f32edba51cf14c851d05c5eea07a9170b9e510a.json", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" } } }, - "055a185452c29b6ca4df318d9059b86b1bd73d14c95904474dde4df581580c4a": { + "6135e800e36d77703eb2e8b9598f683698677115d74aec7f4b25db092f6fd27c": { "source": { "path": "integ-servicecatalog-product.template.json", "packaging": "file" }, "destinations": { - "current_account-current_region": { - "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "055a185452c29b6ca4df318d9059b86b1bd73d14c95904474dde4df581580c4a.json", - "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + "12345678-test-region": { + "bucketName": "cdk-hnb659fds-assets-12345678-test-region", + "objectKey": "6135e800e36d77703eb2e8b9598f683698677115d74aec7f4b25db092f6fd27c.json", + "region": "test-region", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-file-publishing-role-12345678-test-region" } } } diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ-servicecatalog-product.template.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ-servicecatalog-product.template.json index 0a09c45713e79..e52fbab32b1ef 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ-servicecatalog-product.template.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ-servicecatalog-product.template.json @@ -1,5 +1,354 @@ { "Resources": { + "TestPortfolio4AC794EB": { + "Type": "AWS::ServiceCatalog::Portfolio", + "Properties": { + "DisplayName": "TestPortfolio", + "ProviderName": "TestProvider", + "AcceptLanguage": "en", + "Description": "This is our Service Catalog Portfolio" + } + }, + "TestPortfolioPortfolioProductAssociation9c99ebba36fc70F2C75A": { + "Type": "AWS::ServiceCatalog::PortfolioProductAssociation", + "Properties": { + "PortfolioId": { + "Ref": "TestPortfolio4AC794EB" + }, + "ProductId": { + "Ref": "TestProduct7606930B" + } + } + }, + "TestAssetBucket9434EFAE": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "product-stack-asset-bucket-12345678-test-region", + "Tags": [ + { + "Key": "aws-cdk:auto-delete-objects", + "Value": "true" + }, + { + "Key": "aws-cdk:cr-owned:3caa27f4", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "TestAssetBucketPolicy62167ACB": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "TestAssetBucket9434EFAE" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "TestAssetBucket9434EFAE", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TestAssetBucket9434EFAE", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "TestAssetBucketAutoDeleteObjectsCustomResource5A0F8F22": { + "Type": "Custom::S3AutoDeleteObjects", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F", + "Arn" + ] + }, + "BucketName": { + "Ref": "TestAssetBucket9434EFAE" + } + }, + "DependsOn": [ + "TestAssetBucketPolicy62167ACB" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-12345678-test-region", + "S3Key": "33e2651435a0d472a75c1e033c9832b21321d9e56711926b04c5705e5f63874c.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Description": { + "Fn::Join": [ + "", + [ + "Lambda function for auto-deleting objects in ", + { + "Ref": "TestAssetBucket9434EFAE" + }, + " S3 bucket." + ] + ] + } + }, + "DependsOn": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" + ] + }, + "AssetsBucketDeploymentAwsCliLayer9BCEE17F": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "cdk-hnb659fds-assets-12345678-test-region", + "S3Key": "5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510.zip" + }, + "Description": "/opt/awscli/aws" + } + }, + "AssetsBucketDeploymentCustomResource283760D6": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + "cdk-hnb659fds-assets-12345678-test-region", + "cdk-hnb659fds-assets-12345678-test-region" + ], + "SourceObjectKeys": [ + "d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2.zip", + "e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d.zip" + ], + "SourceMarkers": [ + {}, + {} + ], + "DestinationBucketName": { + "Ref": "TestAssetBucket9434EFAE" + }, + "Extract": false, + "Prune": false + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::cdk-hnb659fds-assets-12345678-test-region" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::cdk-hnb659fds-assets-12345678-test-region/*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TestAssetBucket9434EFAE", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TestAssetBucket9434EFAE", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "Roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "cdk-hnb659fds-assets-12345678-test-region", + "S3Key": "2bc265c5e0569aeb24a6349c15bd54e76e845892376515e036627ab0cc70bb64.zip" + }, + "Role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "Handler": "index.handler", + "Layers": [ + { + "Ref": "AssetsBucketDeploymentAwsCliLayer9BCEE17F" + } + ], + "Runtime": "python3.9", + "Timeout": 900 + }, + "DependsOn": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + ] + }, "TestProduct7606930B": { "Type": "AWS::ServiceCatalog::CloudFormationProduct", "Properties": { @@ -16,7 +365,7 @@ "DisableTemplateValidation": false, "Info": { "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e.json" + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e.json" } } }, @@ -24,7 +373,7 @@ "DisableTemplateValidation": false, "Info": { "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5.json" + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5.json" } } }, @@ -32,7 +381,7 @@ "DisableTemplateValidation": false, "Info": { "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" } } }, @@ -40,77 +389,36 @@ "DisableTemplateValidation": false, "Info": { "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" } } }, + { + "DisableTemplateValidation": true, + "Info": { + "LoadTemplateFromURL": { + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/c6a724b9f87d5f730d400d518f32edba51cf14c851d05c5eea07a9170b9e510a.json" + } + }, + "Name": "testAssetProduct" + }, { "DisableTemplateValidation": false, "Info": { "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" } }, "Name": "v1" } ] } - }, - "TestProductTagOptionAssociation0d813eebb333DA3E2F21": { - "Type": "AWS::ServiceCatalog::TagOptionAssociation", - "Properties": { - "ResourceId": { - "Ref": "TestProduct7606930B" - }, - "TagOptionId": { - "Ref": "TagOptions5f31c54ba705F110F743" - } - } - }, - "TestProductTagOptionAssociation5d93a5c977b4B664DD87": { - "Type": "AWS::ServiceCatalog::TagOptionAssociation", - "Properties": { - "ResourceId": { - "Ref": "TestProduct7606930B" - }, - "TagOptionId": { - "Ref": "TagOptions8d263919cebb6764AC10" - } - } - }, - "TestProductTagOptionAssociationcfaf40b186a3E5FDECDC": { - "Type": "AWS::ServiceCatalog::TagOptionAssociation", - "Properties": { - "ResourceId": { - "Ref": "TestProduct7606930B" - }, - "TagOptionId": { - "Ref": "TagOptionsa260cbbd99c416C40F73" - } - } - }, - "TagOptions5f31c54ba705F110F743": { - "Type": "AWS::ServiceCatalog::TagOption", - "Properties": { - "Key": "key1", - "Value": "value1", - "Active": true - } - }, - "TagOptions8d263919cebb6764AC10": { - "Type": "AWS::ServiceCatalog::TagOption", - "Properties": { - "Key": "key1", - "Value": "value2", - "Active": true - } - }, - "TagOptionsa260cbbd99c416C40F73": { - "Type": "AWS::ServiceCatalog::TagOption", - "Properties": { - "Key": "key2", - "Value": "value1", - "Active": true + } + }, + "Outputs": { + "PortfolioId": { + "Value": { + "Ref": "TestPortfolio4AC794EB" } } }, diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ.json index b0af43e71d6f8..2e83475ed3933 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integ.json @@ -1,14 +1,13 @@ { - "version": "20.0.0", + "enableLookups": true, + "version": "22.0.0", "testCases": { - "integ.product": { + "integ-product/DefaultTest": { "stacks": [ "integ-servicecatalog-product" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "integ-product/DefaultTest/DeployAssert", + "assertionStackName": "integproductDefaultTestDeployAssertEB23E2A9" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integproductDefaultTestDeployAssertEB23E2A9.assets.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integproductDefaultTestDeployAssertEB23E2A9.assets.json new file mode 100644 index 0000000000000..95084c763fe0a --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integproductDefaultTestDeployAssertEB23E2A9.assets.json @@ -0,0 +1,19 @@ +{ + "version": "22.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "integproductDefaultTestDeployAssertEB23E2A9.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integproductDefaultTestDeployAssertEB23E2A9.template.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integproductDefaultTestDeployAssertEB23E2A9.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integproductDefaultTestDeployAssertEB23E2A9.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integservicecatalogproductS3AssetProductCED6E119.product.template.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integservicecatalogproductS3AssetProductCED6E119.product.template.json new file mode 100644 index 0000000000000..6be8f345ba1c8 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/integservicecatalogproductS3AssetProductCED6E119.product.template.json @@ -0,0 +1,106 @@ +{ + "Resources": { + "HelloHandlerServiceRole11EF7C63": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "HelloHandler2E4FBA4D": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "product-stack-asset-bucket-12345678-test-region", + "S3Key": "d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HelloHandlerServiceRole11EF7C63", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "python3.9" + }, + "DependsOn": [ + "HelloHandlerServiceRole11EF7C63" + ] + }, + "HelloHandler2ServiceRole37B1402D": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "HelloHandler2109B0120": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "product-stack-asset-bucket-12345678-test-region", + "S3Key": "e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HelloHandler2ServiceRole37B1402D", + "Arn" + ] + }, + "Handler": "index.handler", + "Runtime": "python3.9" + }, + "DependsOn": [ + "HelloHandler2ServiceRole37B1402D" + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/manifest.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/manifest.json index be560c2b18f62..4c11ffe8ad156 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/manifest.json @@ -1,12 +1,6 @@ { - "version": "20.0.0", + "version": "22.0.0", "artifacts": { - "Tree": { - "type": "cdk:tree", - "properties": { - "file": "tree.json" - } - }, "integ-servicecatalog-product.assets": { "type": "cdk:asset-manifest", "properties": { @@ -17,20 +11,20 @@ }, "integ-servicecatalog-product": { "type": "aws:cloudformation:stack", - "environment": "aws://unknown-account/unknown-region", + "environment": "aws://12345678/test-region", "properties": { "templateFile": "integ-servicecatalog-product.template.json", "validateOnSynth": false, - "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", - "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/055a185452c29b6ca4df318d9059b86b1bd73d14c95904474dde4df581580c4a.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-deploy-role-12345678-test-region", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-cfn-exec-role-12345678-test-region", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-12345678-test-region/6135e800e36d77703eb2e8b9598f683698677115d74aec7f4b25db092f6fd27c.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ "integ-servicecatalog-product.assets" ], "lookupRole": { - "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "arn": "arn:${AWS::Partition}:iam::12345678:role/cdk-hnb659fds-lookup-role-12345678-test-region", "requiresBootstrapStackVersion": 8, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" } @@ -39,46 +33,88 @@ "integ-servicecatalog-product.assets" ], "metadata": { - "/integ-servicecatalog-product/TestProduct/Resource": [ + "/integ-servicecatalog-product/TestPortfolio/Resource": [ { "type": "aws:cdk:logicalId", - "data": "TestProduct7606930B" + "data": "TestPortfolio4AC794EB" + } + ], + "/integ-servicecatalog-product/TestPortfolio/PortfolioProductAssociation9c99ebba36fc": [ + { + "type": "aws:cdk:logicalId", + "data": "TestPortfolioPortfolioProductAssociation9c99ebba36fc70F2C75A" } ], - "/integ-servicecatalog-product/TestProduct/TagOptionAssociation0d813eebb333": [ + "/integ-servicecatalog-product/TestAssetBucket/Resource": [ { "type": "aws:cdk:logicalId", - "data": "TestProductTagOptionAssociation0d813eebb333DA3E2F21" + "data": "TestAssetBucket9434EFAE" } ], - "/integ-servicecatalog-product/TestProduct/TagOptionAssociation5d93a5c977b4": [ + "/integ-servicecatalog-product/TestAssetBucket/Policy/Resource": [ { "type": "aws:cdk:logicalId", - "data": "TestProductTagOptionAssociation5d93a5c977b4B664DD87" + "data": "TestAssetBucketPolicy62167ACB" } ], - "/integ-servicecatalog-product/TestProduct/TagOptionAssociationcfaf40b186a3": [ + "/integ-servicecatalog-product/TestAssetBucket/AutoDeleteObjectsCustomResource/Default": [ { "type": "aws:cdk:logicalId", - "data": "TestProductTagOptionAssociationcfaf40b186a3E5FDECDC" + "data": "TestAssetBucketAutoDeleteObjectsCustomResource5A0F8F22" } ], - "/integ-servicecatalog-product/TagOptions/5f31c54ba705": [ + "/integ-servicecatalog-product/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role": [ { "type": "aws:cdk:logicalId", - "data": "TagOptions5f31c54ba705F110F743" + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092" } ], - "/integ-servicecatalog-product/TagOptions/8d263919cebb": [ + "/integ-servicecatalog-product/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler": [ { "type": "aws:cdk:logicalId", - "data": "TagOptions8d263919cebb6764AC10" + "data": "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F" } ], - "/integ-servicecatalog-product/TagOptions/a260cbbd99c4": [ + "/integ-servicecatalog-product/AssetsBucketDeployment/AwsCliLayer/Resource": [ { "type": "aws:cdk:logicalId", - "data": "TagOptionsa260cbbd99c416C40F73" + "data": "AssetsBucketDeploymentAwsCliLayer9BCEE17F" + } + ], + "/integ-servicecatalog-product/AssetsBucketDeployment/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetsBucketDeploymentCustomResource283760D6" + } + ], + "/integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ], + "/integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF" + } + ], + "/integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536" + } + ], + "/integ-servicecatalog-product/TestProduct/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TestProduct7606930B" + } + ], + "/integ-servicecatalog-product/PortfolioId": [ + { + "type": "aws:cdk:logicalId", + "data": "PortfolioId" } ], "/integ-servicecatalog-product/BootstrapVersion": [ @@ -95,6 +131,59 @@ ] }, "displayName": "integ-servicecatalog-product" + }, + "integproductDefaultTestDeployAssertEB23E2A9.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integproductDefaultTestDeployAssertEB23E2A9.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integproductDefaultTestDeployAssertEB23E2A9": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integproductDefaultTestDeployAssertEB23E2A9.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integproductDefaultTestDeployAssertEB23E2A9.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integproductDefaultTestDeployAssertEB23E2A9.assets" + ], + "metadata": { + "/integ-product/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-product/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-product/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/tree.json b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/tree.json index ac285ac183ffe..3b79bde4c8bcc 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.js.snapshot/tree.json @@ -4,18 +4,56 @@ "id": "App", "path": "", "children": { - "Tree": { - "id": "Tree", - "path": "Tree", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" - } - }, "integ-servicecatalog-product": { "id": "integ-servicecatalog-product", "path": "integ-servicecatalog-product", "children": { + "TestPortfolio": { + "id": "TestPortfolio", + "path": "integ-servicecatalog-product/TestPortfolio", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/TestPortfolio/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ServiceCatalog::Portfolio", + "aws:cdk:cloudformation:props": { + "displayName": "TestPortfolio", + "providerName": "TestProvider", + "acceptLanguage": "en", + "description": "This is our Service Catalog Portfolio" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-servicecatalog.CfnPortfolio", + "version": "0.0.0" + } + }, + "PortfolioProductAssociation9c99ebba36fc": { + "id": "PortfolioProductAssociation9c99ebba36fc", + "path": "integ-servicecatalog-product/TestPortfolio/PortfolioProductAssociation9c99ebba36fc", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ServiceCatalog::PortfolioProductAssociation", + "aws:cdk:cloudformation:props": { + "portfolioId": { + "Ref": "TestPortfolio4AC794EB" + }, + "productId": { + "Ref": "TestProduct7606930B" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-servicecatalog.CfnPortfolioProductAssociation", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-servicecatalog.Portfolio", + "version": "0.0.0" + } + }, "SNSTopicProduct3": { "id": "SNSTopicProduct3", "path": "integ-servicecatalog-product/SNSTopicProduct3", @@ -56,6 +94,161 @@ "version": "0.0.0" } }, + "TestAssetBucket": { + "id": "TestAssetBucket", + "path": "integ-servicecatalog-product/TestAssetBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/TestAssetBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketName": "product-stack-asset-bucket-12345678-test-region", + "tags": [ + { + "key": "aws-cdk:auto-delete-objects", + "value": "true" + }, + { + "key": "aws-cdk:cr-owned:3caa27f4", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "integ-servicecatalog-product/TestAssetBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/TestAssetBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "TestAssetBucket9434EFAE" + }, + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:List*" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092", + "Arn" + ] + } + }, + "Resource": [ + { + "Fn::GetAtt": [ + "TestAssetBucket9434EFAE", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TestAssetBucket9434EFAE", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + }, + "AutoDeleteObjectsCustomResource": { + "id": "AutoDeleteObjectsCustomResource", + "path": "integ-servicecatalog-product/TestAssetBucket/AutoDeleteObjectsCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "integ-servicecatalog-product/TestAssetBucket/AutoDeleteObjectsCustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "Custom::S3AutoDeleteObjectsCustomResourceProvider": { + "id": "Custom::S3AutoDeleteObjectsCustomResourceProvider", + "path": "integ-servicecatalog-product/Custom::S3AutoDeleteObjectsCustomResourceProvider", + "children": { + "Staging": { + "id": "Staging", + "path": "integ-servicecatalog-product/Custom::S3AutoDeleteObjectsCustomResourceProvider/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "integ-servicecatalog-product/Custom::S3AutoDeleteObjectsCustomResourceProvider/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "integ-servicecatalog-product/Custom::S3AutoDeleteObjectsCustomResourceProvider/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResourceProvider", + "version": "0.0.0" + } + }, "SNSTopicProduct1": { "id": "SNSTopicProduct1", "path": "integ-servicecatalog-product/SNSTopicProduct1", @@ -120,25 +313,642 @@ "version": "0.0.0" } }, + "S3AssetProduct": { + "id": "S3AssetProduct", + "path": "integ-servicecatalog-product/S3AssetProduct", + "children": { + "HelloHandler": { + "id": "HelloHandler", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Code": { + "id": "Code", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler/Code/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler/Code/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": "product-stack-asset-bucket-12345678-test-region", + "s3Key": "d3833f63e813b3a96ea04c8c50ca98209330867f5f6ac358efca11f85a3476c2.zip" + }, + "role": { + "Fn::GetAtt": [ + "HelloHandlerServiceRole11EF7C63", + "Arn" + ] + }, + "handler": "index.handler", + "runtime": "python3.9" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Function", + "version": "0.0.0" + } + }, + "HelloHandler2": { + "id": "HelloHandler2", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler2", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler2/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler2/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler2/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Code": { + "id": "Code", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler2/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler2/Code/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler2/Code/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/S3AssetProduct/HelloHandler2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": "product-stack-asset-bucket-12345678-test-region", + "s3Key": "e2204c9fddfc339ea362dec9143b4c22f883d4c18dd38c456f8f6a7161c1073d.zip" + }, + "role": { + "Fn::GetAtt": [ + "HelloHandler2ServiceRole37B1402D", + "Arn" + ] + }, + "handler": "index.handler", + "runtime": "python3.9" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Function", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-servicecatalog.ProductStack", + "version": "0.0.0" + } + }, + "AssetsBucketDeployment": { + "id": "AssetsBucketDeployment", + "path": "integ-servicecatalog-product/AssetsBucketDeployment", + "children": { + "AwsCliLayer": { + "id": "AwsCliLayer", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/AwsCliLayer", + "children": { + "Code": { + "id": "Code", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/AwsCliLayer/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/AwsCliLayer/Code/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/AwsCliLayer/Code/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/AwsCliLayer/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::LayerVersion", + "aws:cdk:cloudformation:props": { + "content": { + "s3Bucket": "cdk-hnb659fds-assets-12345678-test-region", + "s3Key": "5d8d1d0aacea23824c62f362e1e3c14b7dd14a31c71b53bfae4d14a6373c5510.zip" + }, + "description": "/opt/awscli/aws" + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnLayerVersion", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/lambda-layer-awscli.AwsCliLayer", + "version": "0.0.0" + } + }, + "CustomResourceHandler": { + "id": "CustomResourceHandler", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/CustomResourceHandler", + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.SingletonFunction", + "version": "0.0.0" + } + }, + "Asset1": { + "id": "Asset1", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/Asset1", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/Asset1/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/Asset1/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "CustomResource": { + "id": "CustomResource", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/CustomResource", + "children": { + "Default": { + "id": "Default", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/CustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + }, + "Asset2": { + "id": "Asset2", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/Asset2", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/Asset2/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/AssetsBucketDeployment/Asset2/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-deployment.BucketDeployment", + "version": "0.0.0" + } + }, + "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C": { + "id": "Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C", + "children": { + "ServiceRole": { + "id": "ServiceRole", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole", + "children": { + "ImportServiceRole": { + "id": "ImportServiceRole", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/ImportServiceRole", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "managedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::cdk-hnb659fds-assets-12345678-test-region" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::cdk-hnb659fds-assets-12345678-test-region/*" + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "TestAssetBucket9434EFAE", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "TestAssetBucket9434EFAE", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Code": { + "id": "Code", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Code/AssetBucket", + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3-assets.Asset", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Lambda::Function", + "aws:cdk:cloudformation:props": { + "code": { + "s3Bucket": "cdk-hnb659fds-assets-12345678-test-region", + "s3Key": "2bc265c5e0569aeb24a6349c15bd54e76e845892376515e036627ab0cc70bb64.zip" + }, + "role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "handler": "index.handler", + "layers": [ + { + "Ref": "AssetsBucketDeploymentAwsCliLayer9BCEE17F" + } + ], + "runtime": "python3.9", + "timeout": 900 + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.CfnFunction", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-lambda.Function", + "version": "0.0.0" + } + }, "TestProduct": { "id": "TestProduct", "path": "integ-servicecatalog-product/TestProduct", "children": { - "Templatebf5098893076": { - "id": "Templatebf5098893076", - "path": "integ-servicecatalog-product/TestProduct/Templatebf5098893076", + "Template1d7080eb134c": { + "id": "Template1d7080eb134c", + "path": "integ-servicecatalog-product/TestProduct/Template1d7080eb134c", "children": { "Stage": { "id": "Stage", - "path": "integ-servicecatalog-product/TestProduct/Templatebf5098893076/Stage", + "path": "integ-servicecatalog-product/TestProduct/Template1d7080eb134c/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { "id": "AssetBucket", - "path": "integ-servicecatalog-product/TestProduct/Templatebf5098893076/AssetBucket", + "path": "integ-servicecatalog-product/TestProduct/Template1d7080eb134c/AssetBucket", "constructInfo": { "fqn": "@aws-cdk/aws-s3.BucketBase", "version": "0.0.0" @@ -150,21 +960,21 @@ "version": "0.0.0" } }, - "Template8a84aa3ab88f": { - "id": "Template8a84aa3ab88f", - "path": "integ-servicecatalog-product/TestProduct/Template8a84aa3ab88f", + "Template346eefaaa30e": { + "id": "Template346eefaaa30e", + "path": "integ-servicecatalog-product/TestProduct/Template346eefaaa30e", "children": { "Stage": { "id": "Stage", - "path": "integ-servicecatalog-product/TestProduct/Template8a84aa3ab88f/Stage", + "path": "integ-servicecatalog-product/TestProduct/Template346eefaaa30e/Stage", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" } }, "AssetBucket": { "id": "AssetBucket", - "path": "integ-servicecatalog-product/TestProduct/Template8a84aa3ab88f/AssetBucket", + "path": "integ-servicecatalog-product/TestProduct/Template346eefaaa30e/AssetBucket", "constructInfo": { "fqn": "@aws-cdk/aws-s3.BucketBase", "version": "0.0.0" @@ -195,7 +1005,7 @@ "disableTemplateValidation": false, "info": { "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e.json" + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e.json" } } }, @@ -203,22 +1013,43 @@ "disableTemplateValidation": false, "info": { "LoadTemplateFromURL": { - "Fn::Sub": "https://s3.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5.json" + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5.json" } } }, { "disableTemplateValidation": false, - "info": {} + "info": { + "LoadTemplateFromURL": { + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" + } + } }, { "disableTemplateValidation": false, - "info": {} + "info": { + "LoadTemplateFromURL": { + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" + } + } + }, + { + "name": "testAssetProduct", + "disableTemplateValidation": true, + "info": { + "LoadTemplateFromURL": { + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/c6a724b9f87d5f730d400d518f32edba51cf14c851d05c5eea07a9170b9e510a.json" + } + } }, { "name": "v1", "disableTemplateValidation": false, - "info": {} + "info": { + "LoadTemplateFromURL": { + "Fn::Sub": "https://s3.test-region.${AWS::URLSuffix}/cdk-hnb659fds-assets-12345678-test-region/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json" + } + } } ] } @@ -227,63 +1058,6 @@ "fqn": "@aws-cdk/aws-servicecatalog.CfnCloudFormationProduct", "version": "0.0.0" } - }, - "TagOptionAssociation0d813eebb333": { - "id": "TagOptionAssociation0d813eebb333", - "path": "integ-servicecatalog-product/TestProduct/TagOptionAssociation0d813eebb333", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ServiceCatalog::TagOptionAssociation", - "aws:cdk:cloudformation:props": { - "resourceId": { - "Ref": "TestProduct7606930B" - }, - "tagOptionId": { - "Ref": "TagOptions5f31c54ba705F110F743" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-servicecatalog.CfnTagOptionAssociation", - "version": "0.0.0" - } - }, - "TagOptionAssociation5d93a5c977b4": { - "id": "TagOptionAssociation5d93a5c977b4", - "path": "integ-servicecatalog-product/TestProduct/TagOptionAssociation5d93a5c977b4", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ServiceCatalog::TagOptionAssociation", - "aws:cdk:cloudformation:props": { - "resourceId": { - "Ref": "TestProduct7606930B" - }, - "tagOptionId": { - "Ref": "TagOptions8d263919cebb6764AC10" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-servicecatalog.CfnTagOptionAssociation", - "version": "0.0.0" - } - }, - "TagOptionAssociationcfaf40b186a3": { - "id": "TagOptionAssociationcfaf40b186a3", - "path": "integ-servicecatalog-product/TestProduct/TagOptionAssociationcfaf40b186a3", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ServiceCatalog::TagOptionAssociation", - "aws:cdk:cloudformation:props": { - "resourceId": { - "Ref": "TestProduct7606930B" - }, - "tagOptionId": { - "Ref": "TagOptionsa260cbbd99c416C40F73" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-servicecatalog.CfnTagOptionAssociation", - "version": "0.0.0" - } } }, "constructInfo": { @@ -291,74 +1065,102 @@ "version": "0.0.0" } }, - "TagOptions": { - "id": "TagOptions", - "path": "integ-servicecatalog-product/TagOptions", + "PortfolioId": { + "id": "PortfolioId", + "path": "integ-servicecatalog-product/PortfolioId", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-servicecatalog-product/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-servicecatalog-product/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "integ-product": { + "id": "integ-product", + "path": "integ-product", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "integ-product/DefaultTest", "children": { - "5f31c54ba705": { - "id": "5f31c54ba705", - "path": "integ-servicecatalog-product/TagOptions/5f31c54ba705", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ServiceCatalog::TagOption", - "aws:cdk:cloudformation:props": { - "key": "key1", - "value": "value1", - "active": true - } - }, + "Default": { + "id": "Default", + "path": "integ-product/DefaultTest/Default", "constructInfo": { - "fqn": "@aws-cdk/aws-servicecatalog.CfnTagOption", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.189" } }, - "8d263919cebb": { - "id": "8d263919cebb", - "path": "integ-servicecatalog-product/TagOptions/8d263919cebb", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ServiceCatalog::TagOption", - "aws:cdk:cloudformation:props": { - "key": "key1", - "value": "value2", - "active": true - } - }, - "constructInfo": { - "fqn": "@aws-cdk/aws-servicecatalog.CfnTagOption", - "version": "0.0.0" - } - }, - "a260cbbd99c4": { - "id": "a260cbbd99c4", - "path": "integ-servicecatalog-product/TagOptions/a260cbbd99c4", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::ServiceCatalog::TagOption", - "aws:cdk:cloudformation:props": { - "key": "key2", - "value": "value1", - "active": true + "DeployAssert": { + "id": "DeployAssert", + "path": "integ-product/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-product/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-product/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-servicecatalog.CfnTagOption", + "fqn": "@aws-cdk/core.Stack", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-servicecatalog.TagOptions", + "fqn": "@aws-cdk/integ-tests.IntegTestCase", "version": "0.0.0" } } }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.1.189" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts index 1bd5d03ea260d..6d32d6aad82ff 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts @@ -1,11 +1,72 @@ import * as path from 'path'; +import * as lambda from '@aws-cdk/aws-lambda'; +import * as s3 from '@aws-cdk/aws-s3'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; import * as servicecatalog from '../lib'; -import { ProductStackHistory } from '../lib'; +import { ProductStackHistory, ProductStackProps } from '../lib'; + +/** + * Follow these instructions to manually test provisioning a Product with an Asset with the resources provisioned in this stack: + * + * 1. Deploy the stack: + ``` + $ cdk deploy --app "node integ.product.js" integ-servicecatalog-product + ``` + * + * 2. Obtain IAM Principal ARN that will provision product. + One way this can be done is by using + ``` + $ aws sts get-caller-identity + ``` + * + * 3. Associate your principal to your portfolio. PortfolioId is stored as an output values from the deployed stack. + ``` + $ aws servicecatalog associate-principal-with-portfolio \ + --portfolio-id= \ + --principal-arn= \ + --principal-type=IAM + ``` + * + * 4. Provision Product using the following prefilled values. + ``` + $ aws servicecatalog provision-product \ + --provisioned-product-name=testAssetProvisioningProduct \ + --product-name=testProduct \ + --provisioning-artifact-name=testAssetProduct + ``` + * + * 5. Verify Provision Product was provisioned providing the ProvisionedProductId from the previous step. + ``` + $ aws servicecatalog describe-provisioned-product --id= + ``` + * + * 6. Terminate Provisioned Product providing the ProvisionedProductId from the previous step. + ``` + $ aws servicecatalog terminate-provisioned-product --provisioned-product-id= + ``` + * + * 7. Disassociate your principal from your portfolio. + ``` + $ aws servicecatalog disassociate-principal-from-portfolio \ + --portfolio-id= \ + --principal-arn= \ + ``` + * + * 8. Destroy the stack: + ``` + $ cdk destroy --app "node integ.product.js" integ-servicecatalog-product + ``` + */ const app = new cdk.App(); -const stack = new cdk.Stack(app, 'integ-servicecatalog-product'); +const stack = new cdk.Stack(app, 'integ-servicecatalog-product', { + env: { + account: process.env.CDK_INTEG_ACCOUNT ?? process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_INTEG_REGION ?? process.env.CDK_DEFAULT_REGION, + }, +}); class TestProductStack extends servicecatalog.ProductStack { constructor(scope: any, id: string) { @@ -15,10 +76,41 @@ class TestProductStack extends servicecatalog.ProductStack { } } +const portfolio = new servicecatalog.Portfolio(stack, 'TestPortfolio', { + displayName: 'TestPortfolio', + providerName: 'TestProvider', + description: 'This is our Service Catalog Portfolio', + messageLanguage: servicecatalog.MessageLanguage.EN, +}); + +class TestAssetProductStack extends servicecatalog.ProductStack { + constructor(scope: any, id: string, props?: ProductStackProps) { + super(scope, id, props); + + new lambda.Function(this, 'HelloHandler', { + runtime: lambda.Runtime.PYTHON_3_9, + code: lambda.Code.fromAsset('./assets'), + handler: 'index.handler', + }); + + new lambda.Function(this, 'HelloHandler2', { + runtime: lambda.Runtime.PYTHON_3_9, + code: lambda.Code.fromAsset('./assetsv2'), + handler: 'index.handler', + }); + } +} + const productStackHistory = new ProductStackHistory(stack, 'ProductStackHistory', { productStack: new TestProductStack(stack, 'SNSTopicProduct3'), currentVersionName: 'v1', - currentVersionLocked: true, + currentVersionLocked: false, +}); + +const testAssetBucket = new s3.Bucket(stack, 'TestAssetBucket', { + bucketName: `product-stack-asset-bucket-${stack.account}-${stack.region}`, + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, }); const product = new servicecatalog.CloudFormationProduct(stack, 'TestProduct', { @@ -42,17 +134,24 @@ const product = new servicecatalog.CloudFormationProduct(stack, 'TestProduct', { { cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new TestProductStack(stack, 'SNSTopicProduct2')), }, + { + productVersionName: 'testAssetProduct', + validateTemplate: false, + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new TestAssetProductStack(stack, 'S3AssetProduct', { + assetBucket: testAssetBucket, + })), + }, productStackHistory.currentVersion(), ], }); -const tagOptions = new servicecatalog.TagOptions(stack, 'TagOptions', { - allowedValuesForTags: { - key1: ['value1', 'value2'], - key2: ['value1'], - }, +new IntegTest(app, 'integ-product', { + testCases: [stack], + enableLookups: true, }); -product.associateTagOptions(tagOptions); +portfolio.addProduct(product); + +new cdk.CfnOutput(stack, 'PortfolioId', { value: portfolio.portfolioId }); app.synth(); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts index 1785393db5afd..f5942464ef631 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/portfolio.test.ts @@ -1,5 +1,6 @@ import { Template } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; +import * as s3 from '@aws-cdk/aws-s3'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; import * as servicecatalog from '../lib'; @@ -194,6 +195,62 @@ describe('Portfolio', () => { }); }), + test('portfolio share with assets', () => { + const assetBucket = new s3.Bucket(stack, 'MyProductStackAssetBucket', { + bucketName: 'test-asset-bucket', + }); + + const productStack = new servicecatalog.ProductStack(stack, 'MyProductStack', { + }); + + const product = new servicecatalog.CloudFormationProduct(stack, 'MyProduct', { + productName: 'testProduct', + owner: 'testOwner', + productVersions: [ + { + productVersionName: 'v1', + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(productStack), + }, + ], + }); + + product.assetBuckets.push(assetBucket); + + const shareAccountId = '012345678901'; + + portfolio.addProduct(product); + portfolio.shareWithAccount(shareAccountId); + + Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalog::PortfolioShare', { + AccountId: shareAccountId, + }); + Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + BucketName: 'test-asset-bucket', + }); + Template.fromStack(stack).hasResourceProperties('AWS::S3::BucketPolicy', { + PolicyDocument: { + Statement: [{ + Effect: 'Allow', + Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::012345678901:root', + ], + ], + }, + }, + }], + }, + }); + }), + test('portfolio share with share tagOptions', () => { const shareAccountId = '012345678901'; diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts index c2c008b4a63e9..e9caacbce81ed 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; +import * as s3 from '@aws-cdk/aws-s3'; import * as s3_assets from '@aws-cdk/aws-s3-assets'; import * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; @@ -8,19 +9,71 @@ import * as servicecatalog from '../lib'; /* eslint-disable quote-props */ describe('ProductStack', () => { - test('fails to add asset to a product stack', () => { + test('Asset bucket undefined in product stack without assets', () => { // GIVEN const app = new cdk.App(); const mainStack = new cdk.Stack(app, 'MyStack'); const productStack = new servicecatalog.ProductStack(mainStack, 'MyProductStack'); + // THEN + expect(productStack._getAssetBucket()).toBeUndefined(); + }), + + test('Used defined Asset bucket in product stack with assets', () => { + // GIVEN + const app = new cdk.App( + { outdir: 'cdk.out' }, + ); + const mainStack = new cdk.Stack(app, 'MyStack'); + const testAssetBucket = new s3.Bucket(mainStack, 'TestAssetBucket', { + bucketName: 'test-asset-bucket', + }); + const productStack = new servicecatalog.ProductStack(mainStack, 'MyProductStack', { + assetBucket: testAssetBucket, + }); + + // WHEN + new s3_assets.Asset(productStack, 'testAsset', { + path: path.join(__dirname, 'assets'), + }); + + // THEN + expect(productStack._getAssetBucket()).toBeDefined(); + }); + + test('fails if bucketName is not specified in product stack with assets', () => { + // GIVEN + const app = new cdk.App( + { outdir: 'cdk.out' }, + ); + const mainStack = new cdk.Stack(app, 'MyStack'); + const testAssetBucket = new s3.Bucket(mainStack, 'TestAssetBucket', { + }); + const productStack = new servicecatalog.ProductStack(mainStack, 'MyProductStack', { + assetBucket: testAssetBucket, + }); + // THEN expect(() => { new s3_assets.Asset(productStack, 'testAsset', { - path: path.join(__dirname, 'product1.template.json'), + path: path.join(__dirname, 'assets'), }); - }).toThrow(/Service Catalog Product Stacks cannot use Assets/); - }), + }).toThrow('A bucketName must be provided to use Assets'); + }); + + test('fails if Asset bucket is not defined in product stack with assets', () => { + // GIVEN + const app = new cdk.App(); + const mainStack = new cdk.Stack(app, 'MyStack'); + const productStack = new servicecatalog.ProductStack(mainStack, 'MyProductStack'); + + // THEN + expect(() => { + new s3_assets.Asset(productStack, 'testAsset', { + path: path.join(__dirname, 'assets'), + }); + }).toThrow('An Asset Bucket must be provided to use Assets'); + }); test('fails if defined at root', () => { // GIVEN diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..8ecc185e9dbee --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"21.0.0"} \ No newline at end of file