diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts index 49785c5093b33..ddb80173f5694 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts +++ b/packages/@aws-cdk/aws-s3-deployment/lib/bucket-deployment.ts @@ -38,6 +38,13 @@ export interface BucketDeploymentProps { */ readonly destinationKeyPrefix?: string; + /** + * If this is set, the zip file will be synced to the destination S3 bucket and unzipped. + * If false, the file will remain zipped in the destination bucket. + * @default true + */ + readonly unzipFile?: boolean; + /** * If this is set, matching files or objects will be excluded from the deployment's sync * command. This can be used to exclude a file from being pruned in the destination bucket. @@ -349,6 +356,7 @@ export class BucketDeployment extends Construct { SourceMarkers: hasMarkers ? sources.map(source => source.markers ?? {}) : undefined, DestinationBucketName: props.destinationBucket.bucketName, DestinationBucketKeyPrefix: props.destinationKeyPrefix, + UnzipFile: props.unzipFile, RetainOnDelete: props.retainOnDelete, Prune: props.prune ?? true, Exclude: props.exclude, diff --git a/packages/@aws-cdk/aws-s3-deployment/lib/lambda/index.py b/packages/@aws-cdk/aws-s3-deployment/lib/lambda/index.py index 877b78a8452ee..b06245435dab5 100644 --- a/packages/@aws-cdk/aws-s3-deployment/lib/lambda/index.py +++ b/packages/@aws-cdk/aws-s3-deployment/lib/lambda/index.py @@ -49,6 +49,7 @@ def cfn_error(message=None): source_markers = props.get('SourceMarkers', None) dest_bucket_name = props['DestinationBucketName'] dest_bucket_prefix = props.get('DestinationBucketKeyPrefix', '') + unzip_file = props.get('UnzipFile', 'true') == 'true' retain_on_delete = props.get('RetainOnDelete', "true") == "true" distribution_id = props.get('DistributionId', '') user_metadata = props.get('UserMetadata', {}) @@ -113,7 +114,7 @@ def cfn_error(message=None): 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) + s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, unzip_file) if distribution_id: cloudfront_invalidate(distribution_id, distribution_paths) @@ -130,7 +131,7 @@ def cfn_error(message=None): #--------------------------------------------------------------------------------------------------- # 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): +def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, exclude, include, source_markers, unzip_file): # 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") @@ -154,12 +155,15 @@ def s3_deploy(s3_source_zips, s3_dest, user_metadata, system_metadata, prune, ex s3_source_zip = s3_source_zips[i] markers = source_markers[i] - 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) + if (unzip_file): + 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: + aws_command("s3", "cp", s3_source_zip, contents_dir) # sync from "contents" to destination diff --git a/packages/@aws-cdk/aws-servicecatalog/README.md b/packages/@aws-cdk/aws-servicecatalog/README.md index 73bc9764ec296..8aa0f3c98c3ad 100644 --- a/packages/@aws-cdk/aws-servicecatalog/README.md +++ b/packages/@aws-cdk/aws-servicecatalog/README.md @@ -185,6 +185,36 @@ const product = new servicecatalog.CloudFormationProduct(this, 'Product', { }); ``` +You can now 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. + +```ts +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; + +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 product = new servicecatalog.CloudFormationProduct(this, 'Product', { + productName: "My Product", + owner: "Product Owner", + productVersions: [ + { + productVersionName: "v1", + cloudFormationTemplate: servicecatalog.CloudFormationTemplate.fromProductStack(new LambdaProduct(this, 'LambdaFunctionProduct')), + }, + ], +}); +``` + ### 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..17b5fcd92415c 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts @@ -1,4 +1,5 @@ import * as s3_assets from '@aws-cdk/aws-s3-assets'; +import { IBucket } from '@aws-cdk/aws-s3'; import { Construct } from 'constructs'; import { hashValues } from './private/util'; import { ProductStack } from './product-stack'; @@ -46,9 +47,15 @@ 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 - + */ + readonly assetBucket?: IBucket; } /** @@ -108,6 +115,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 376824d664af6..195f0521a39c0 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 * as sns from '@aws-cdk/aws-sns'; import * as cdk from '@aws-cdk/core'; -import { Construct } from 'constructs'; +import { IBucket } from '@aws-cdk/aws-s3'; +import { Construct, IConstruct } from 'constructs'; import { MessageLanguage } from './common'; import { CloudFormationRuleConstraintOptions, CommonConstraintOptions, @@ -81,6 +82,11 @@ export interface IPortfolio extends cdk.IResource { */ addProduct(product: IProduct): void; + /** + * Grants all shared accounts read permissions to each asset bucket. + */ + giveAssetBucketsReadToSharedAccounts(): void; + /** * Associate Tag Options. * A TagOption is a key-value pair managed in AWS Service Catalog. @@ -105,7 +111,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 +161,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,10 +177,14 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { } public addProduct(product: IProduct): void { + if (product.assetBucket) { + this.assetBuckets.add(product.assetBucket); + } AssociationManager.associateProductWithPortfolio(this, product, undefined); } public shareWithAccount(accountId: string, options: PortfolioShareOptions = {}): void { + this.sharedAccounts.push(accountId); const hashId = this.generateUniqueHash(accountId); new CfnPortfolioShare(this, `PortfolioShare${hashId}`, { portfolioId: this.portfolioId, @@ -220,6 +232,14 @@ abstract class PortfolioBase extends cdk.Resource implements IPortfolio { AssociationManager.deployWithStackSets(this, product, options); } + public giveAssetBucketsReadToSharedAccounts() { + for (const bucket of this.assetBuckets) { + for (const accountId of this.sharedAccounts) { + bucket.grantRead(new iam.AccountPrincipal(accountId)); + } + } + } + /** * Associate a principal with the portfolio. * If the principal is already associated, it will skip. @@ -336,6 +356,7 @@ export class Portfolio extends PortfolioBase { if (props.tagOptions !== undefined) { this.associateTagOptions(props.tagOptions); } + cdk.Aspects.of(this).add(new PortfolioAssetBucketSharer()); } protected generateUniqueHash(value: string): string { @@ -348,3 +369,12 @@ export class Portfolio extends PortfolioBase { InputValidator.validateLength(this.node.path, 'portfolio description', 0, 2000, props.description); } } + +class PortfolioAssetBucketSharer implements cdk.IAspect { + public visit(node: IConstruct): void { + if (node instanceof Portfolio) { + const portfolio = node as Portfolio; + portfolio.giveAssetBucketsReadToSharedAccounts(); + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-asset-bucket.ts b/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-asset-bucket.ts new file mode 100644 index 0000000000000..01e76842b6fbe --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/lib/private/product-stack-asset-bucket.ts @@ -0,0 +1,83 @@ +import * as cdk from '@aws-cdk/core'; +import { IBucket, BlockPublicAccess, Bucket } from '../../../aws-s3'; +import { BucketDeployment, ISource, Source } from '../../../aws-s3-deployment'; +import { Construct } from 'constructs'; +import { hashValues } from './util'; + +/** + * A Service Catalog product stack asset bucket, which is similar in form to an Amazon S3 bucket. + * You can store multiple product stack assets and collectively deploy them to S3. + */ +export class ProductStackAssetBucket extends Construct { + private readonly bucketName: string; + private readonly bucket: IBucket; + private readonly assets: ISource[]; + + constructor(scope: Construct, id: string) { + super(scope, id); + + this.bucketName = this.generateBucketName(id); + + this.bucket = new Bucket(scope, `${id}S3Bucket`, { + bucketName: this.bucketName, + blockPublicAccess: BlockPublicAccess.BLOCK_ALL, + }); + + this.assets = []; + } + + /** + * Fetch the S3 bucket. + * + * @internal + */ + public getBucket(): IBucket { + return this.bucket; + } + + /** + * Generate unique name for S3 bucket. + * + * @internal + */ + private generateBucketName(id: string): string { + const accountId = cdk.Stack.of(this).account; + if (cdk.Token.isUnresolved(accountId)) { + throw new Error('CDK Account ID must be defined in the application environment'); + } + return `product-stack-asset-bucket-${accountId}-${hashValues(id)}`; + } + + /** + * Fetch the expected S3 location of an asset. + * + * @internal + */ + public addAsset(asset: cdk.FileAssetSource): cdk.FileAssetLocation { + const assetPath = './cdk.out/' + asset.fileName; + this.assets.push(Source.asset(assetPath)); + + const bucketName = this.bucketName; + 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 }; + } + + /** + * Deploy all assets to S3. + * + * @internal + */ + public deployAssets() { + if (this.assets.length > 0) { + new BucketDeployment(this, 'AssetsBucketDeployment', { + sources: this.assets, + destinationBucket: this.bucket, + unzipFile: false, + }); + } + } +} 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 4840a7e756fe5..c573796542d1a 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 * as cdk from '@aws-cdk/core'; +import { ProductStack } from '../product-stack'; +import { ProductStackAssetBucket } from './product-stack-asset-bucket'; +import { hashValues } from './util'; /** * Deployment environment for an AWS Service Catalog product stack. @@ -7,6 +10,7 @@ import * as cdk from '@aws-cdk/core'; */ export class ProductStackSynthesizer extends cdk.StackSynthesizer { private stack?: cdk.Stack; + private assetBucket?: ProductStackAssetBucket; public bind(stack: cdk.Stack): void { if (this.stack !== undefined) { @@ -16,17 +20,28 @@ export class ProductStackSynthesizer extends cdk.StackSynthesizer { } public addFileAsset(_asset: cdk.FileAssetSource): cdk.FileAssetLocation { - throw new Error('Service Catalog Product Stacks cannot use Assets'); + if (!this.stack) { + throw new Error('You must call bindStack() first'); + } + + if (!this.assetBucket) { + const parentStack = (this.stack as ProductStack)._getParentStack(); + this.assetBucket = new ProductStackAssetBucket(parentStack, `ProductStackAssetBucket${hashValues(this.stack.stackName)}`); + (this.stack as ProductStack)._setAssetBucket(this.assetBucket.getBucket()); + } + + return this.assetBucket.addAsset(_asset); } public addDockerImageAsset(_asset: cdk.DockerImageAssetSource): cdk.DockerImageAssetLocation { - throw new Error('Service Catalog Product Stacks cannot use Assets'); + throw new Error('Service Catalog Product Stacks cannot use DockerImageAssets'); } public synthesize(session: cdk.ISynthesisSession): void { if (!this.stack) { throw new Error('You must call bindStack() first'); } + this.assetBucket?.deployAssets(); // Synthesize the template, but don't emit as a cloud assembly artifact. // It will be registered as an S3 asset of its parent instead. this.synthesizeStackTemplate(this.stack, session); diff --git a/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts b/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts index 216b0b90b1d12..c21ed85ccd4a0 100644 --- a/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts +++ b/packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts @@ -1,7 +1,8 @@ +import * as cdk from '@aws-cdk/core'; import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; -import * as cdk from '@aws-cdk/core'; +import { IBucket } from '@aws-cdk/aws-s3'; import { Construct } from 'constructs'; import { ProductStackSynthesizer } from './private/product-stack-synthesizer'; import { ProductStackHistory } from './product-stack-history'; @@ -20,6 +21,7 @@ export class ProductStack extends cdk.Stack { private _parentProductStackHistory?: ProductStackHistory; private _templateUrl?: string; private _parentStack: cdk.Stack; + private assetBucket?: IBucket; constructor(scope: Construct, id: string) { super(scope, id, { @@ -50,6 +52,33 @@ 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; + } + + /** + * Set the asset bucket. + * + * @internal + */ + public _setAssetBucket(assetBucket: IBucket) { + this.assetBucket = assetBucket; + } + /** * 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..cb0964406c216 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 bucket of a product created via product stack. + * @atrribute + * @default - No asset bucket provided. + */ + readonly assetBucket?: IBucket; + /** * Associate Tag Options. * A TagOption is a key-value pair managed in AWS Service Catalog. @@ -172,6 +180,13 @@ export class CloudFormationProduct extends Product { public readonly productArn: string; public readonly productId: string; + /** + * The asset bucket of a product created via product stack. + * @param + * @default - No asset bucket provided. + */ + public assetBucket?: IBucket; + constructor(scope: Construct, id: string, props: CloudFormationProductProps) { super(scope, id); @@ -206,6 +221,7 @@ export class CloudFormationProduct extends Product { props: CloudFormationProductProps): CfnCloudFormationProduct.ProvisioningArtifactPropertiesProperty[] { return props.productVersions.map(productVersion => { const template = productVersion.cloudFormationTemplate.bind(this); + this.assetBucket = 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..3e3d4f19dfbd0 100644 --- a/packages/@aws-cdk/aws-servicecatalog/package.json +++ b/packages/@aws-cdk/aws-servicecatalog/package.json @@ -89,7 +89,9 @@ }, "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 +99,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/cdk.out/asset.3be8ad230b47f23554e7098c40e6e4f58ffc7c0cdddbf0da8c8cc105d6d25f2d.zip b/packages/@aws-cdk/aws-servicecatalog/test/cdk.out/asset.3be8ad230b47f23554e7098c40e6e4f58ffc7c0cdddbf0da8c8cc105d6d25f2d.zip new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk/aws-servicecatalog/test/cdk.out/asset.ed2df879ea11a7ba272ba28036479b1f6ad0026e4df8bd984dff4bfe3b2e1f77.zip b/packages/@aws-cdk/aws-servicecatalog/test/cdk.out/asset.ed2df879ea11a7ba272ba28036479b1f6ad0026e4df8bd984dff4bfe3b2e1f77.zip new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts index 1bd5d03ea260d..7638663b96923 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/integ.product.ts @@ -1,17 +1,24 @@ import * as path from 'path'; +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'; import * as servicecatalog from '../lib'; import { ProductStackHistory } from '../lib'; 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_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, +}); class TestProductStack extends servicecatalog.ProductStack { constructor(scope: any, id: string) { super(scope, id); new sns.Topic(this, 'TopicProduct'); + + new s3_assets.Asset(this, 'testAsset', { + path: path.join(__dirname, 'products.template.zip'), + }); } } diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product-stack-asset-bucket.test.ts b/packages/@aws-cdk/aws-servicecatalog/test/product-stack-asset-bucket.test.ts new file mode 100644 index 0000000000000..9709374aee1f1 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/product-stack-asset-bucket.test.ts @@ -0,0 +1,62 @@ +import { Template } from '@aws-cdk/assertions'; +import * as cdk from '@aws-cdk/core'; +import { FileAssetSource } from '@aws-cdk/core'; +import { ProductStackAssetBucket } from '../lib/private/product-stack-asset-bucket'; +import { hashValues } from '../lib/private/util'; + +describe('ProductStackAssetBucket', () => { + let app: cdk.App; + let stack: cdk.Stack; + + beforeEach(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'Stack', { + env: { account: '123456789876' }, + }); + }); + + test('default ProductStackAssetBucket creation', () => { + // WHEN + new ProductStackAssetBucket(stack, 'MyProductStackAssetBucket'); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + BucketName: `product-stack-asset-bucket-${stack.account}-${hashValues('MyProductStackAssetBucket')}`, + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true, + }, + }); + }), + + test('ProductStackAssetBucket without assets avoids bucket deployment', () => { + // GIVEN + const assetBucket = new ProductStackAssetBucket(stack, 'MyProductStackAssetBucket'); + + // WHEN + assetBucket.deployAssets(); + + // THEN + Template.fromStack(stack).resourceCountIs('Custom::CDKBucketDeployment', 0); + }), + + test('ProductStackAssetBucket with assets creates bucket deployment', () => { + // GIVEN + const assetBucket = new ProductStackAssetBucket(stack, 'MyProductStackAssetBucket'); + + const asset = { + packaging: 'zip', + sourceHash: '3be8ad230b47f23554e7098c40e6e4f58ffc7c0cdddbf0da8c8cc105d6d25f2d', + fileName: '../test/cdk.out/asset.3be8ad230b47f23554e7098c40e6e4f58ffc7c0cdddbf0da8c8cc105d6d25f2d.zip', + } as FileAssetSource; + + // WHEN + assetBucket.addAsset(asset); + assetBucket.deployAssets(); + + // THEN + Template.fromStack(stack).resourceCountIs('Custom::CDKBucketDeployment', 1); + }); +}); \ No newline at end of file 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..5345d9cc4194e 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts +++ b/packages/@aws-cdk/aws-servicecatalog/test/product-stack.test.ts @@ -8,20 +8,35 @@ 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 mainStack = new cdk.Stack(app, 'MyStack', { + env: { account: '123456789876' }, + }); const productStack = new servicecatalog.ProductStack(mainStack, 'MyProductStack'); // THEN - expect(() => { - new s3_assets.Asset(productStack, 'testAsset', { - path: path.join(__dirname, 'product1.template.json'), - }); - }).toThrow(/Service Catalog Product Stacks cannot use Assets/); + expect(productStack._getAssetBucket()).toBeUndefined(); }), + test('Asset bucked defined in product stack with assets', () => { + // GIVEN + const app = new cdk.App(); + const mainStack = new cdk.Stack(app, 'MyStack', { + env: { account: '123456789876' }, + }); + const productStack = new servicecatalog.ProductStack(mainStack, 'MyProductStack'); + + // WHEN + new s3_assets.Asset(productStack, 'testAsset', { + path: path.join(__dirname, 'product1.template.json'), + }); + + // THEN + expect(productStack._getAssetBucket()).toBeDefined(); + }); + test('fails if defined at root', () => { // GIVEN const app = new cdk.App(); diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df.zip b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df.zip new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.ed2df879ea11a7ba272ba28036479b1f6ad0026e4df8bd984dff4bfe3b2e1f77.zip b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.ed2df879ea11a7ba272ba28036479b1f6ad0026e4df8bd984dff4bfe3b2e1f77.zip new file mode 100644 index 0000000000000..082d24089e5ff Binary files /dev/null and b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.ed2df879ea11a7ba272ba28036479b1f6ad0026e4df8bd984dff4bfe3b2e1f77.zip differ diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6/index.py b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6/index.py new file mode 100644 index 0000000000000..b06245435dab5 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6/index.py @@ -0,0 +1,307 @@ +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', '') + unzip_file = props.get('UnzipFile', '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, unzip_file) + + 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') + }) + 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, unzip_file): + # 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 (unzip_file): + 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: + 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'] + logger.info(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) \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f.zip b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f.zip new file mode 100644 index 0000000000000..b8d995cd10454 Binary files /dev/null and b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/asset.fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f.zip differ 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 index 2efc89439fab8..588d7b269d34f 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/cdk.out +++ b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/cdk.out @@ -1 +1 @@ -{"version":"18.0.0"} \ No newline at end of file +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/integ-servicecatalog-product.assets.json b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/integ-servicecatalog-product.assets.json new file mode 100644 index 0000000000000..7a2e4d88f5844 --- /dev/null +++ b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/integ-servicecatalog-product.assets.json @@ -0,0 +1,104 @@ +{ + "version": "20.0.0", + "files": { + "b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e": { + "source": { + "path": "asset.b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e.json", + "packaging": "file" + }, + "destinations": { + "366234375154-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-366234375154-us-east-1", + "objectKey": "b59f768286e16b69628bb23b9c1a1f07300a24101b8979d8e2a94ff1ab03d09e.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::366234375154:role/cdk-hnb659fds-file-publishing-role-366234375154-us-east-1" + } + } + }, + "6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5": { + "source": { + "path": "asset.6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5.json", + "packaging": "file" + }, + "destinations": { + "366234375154-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-366234375154-us-east-1", + "objectKey": "6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::366234375154:role/cdk-hnb659fds-file-publishing-role-366234375154-us-east-1" + } + } + }, + "fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f": { + "source": { + "path": "asset.fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f.zip", + "packaging": "file" + }, + "destinations": { + "366234375154-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-366234375154-us-east-1", + "objectKey": "fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f.zip", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::366234375154:role/cdk-hnb659fds-file-publishing-role-366234375154-us-east-1" + } + } + }, + "f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6": { + "source": { + "path": "asset.f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6", + "packaging": "zip" + }, + "destinations": { + "366234375154-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-366234375154-us-east-1", + "objectKey": "f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6.zip", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::366234375154:role/cdk-hnb659fds-file-publishing-role-366234375154-us-east-1" + } + } + }, + "95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df": { + "source": { + "path": "asset.95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df.zip", + "packaging": "file" + }, + "destinations": { + "366234375154-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-366234375154-us-east-1", + "objectKey": "95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df.zip", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::366234375154:role/cdk-hnb659fds-file-publishing-role-366234375154-us-east-1" + } + } + }, + "dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f": { + "source": { + "path": "integservicecatalogproductSNSTopicProduct24C7C16DA.product.template.json", + "packaging": "file" + }, + "destinations": { + "366234375154-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-366234375154-us-east-1", + "objectKey": "dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::366234375154:role/cdk-hnb659fds-file-publishing-role-366234375154-us-east-1" + } + } + }, + "cb24c24c0830592361c602b75a469dee05e5a9a62714ecf2ad6aa0ff8f6f2131": { + "source": { + "path": "integ-servicecatalog-product.template.json", + "packaging": "file" + }, + "destinations": { + "366234375154-us-east-1": { + "bucketName": "cdk-hnb659fds-assets-366234375154-us-east-1", + "objectKey": "cb24c24c0830592361c602b75a469dee05e5a9a62714ecf2ad6aa0ff8f6f2131.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::366234375154:role/cdk-hnb659fds-file-publishing-role-366234375154-us-east-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/integ-servicecatalog-product.template.json b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/integ-servicecatalog-product.template.json index 11578cd12197b..b46131f3360de 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/integ-servicecatalog-product.template.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/integ-servicecatalog-product.template.json @@ -1,5 +1,353 @@ { "Resources": { + "ProductStackAssetBucket9033b902ec6bAssetsBucketDeploymentAwsCliLayerB5D137F9": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3Bucket8D4A7808" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC" + } + ] + } + ] + } + ] + ] + } + }, + "Description": "/opt/awscli/aws" + } + }, + "ProductStackAssetBucket9033b902ec6bAssetsBucketDeploymentCustomResourceBB54446A": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3Bucket5D5509D9" + } + ], + "SourceObjectKeys": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080" + } + ] + } + ] + } + ] + ] + } + ], + "DestinationBucketName": { + "Ref": "ProductStackAssetBucket9033b902ec6bS3BucketAAEEA885" + }, + "UnzipFile": false, + "Prune": true + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ProductStackAssetBucket9033b902ec6bS3BucketAAEEA885": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "product-stack-asset-bucket-366234375154-9d1b9ad6bf99", + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "ProductStackAssetBucketd0571cd28fb8AssetsBucketDeploymentAwsCliLayer450C5C9E": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3Bucket8D4A7808" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC" + } + ] + } + ] + } + ] + ] + } + }, + "Description": "/opt/awscli/aws" + } + }, + "ProductStackAssetBucketd0571cd28fb8AssetsBucketDeploymentCustomResourceE58C85B1": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3Bucket5D5509D9" + } + ], + "SourceObjectKeys": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080" + } + ] + } + ] + } + ] + ] + } + ], + "DestinationBucketName": { + "Ref": "ProductStackAssetBucketd0571cd28fb8S3Bucket1257A20B" + }, + "UnzipFile": false, + "Prune": true + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ProductStackAssetBucketd0571cd28fb8S3Bucket1257A20B": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "product-stack-asset-bucket-366234375154-9ffa45bce4c1", + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "ProductStackAssetBucket7d24e9c6ae26AssetsBucketDeploymentAwsCliLayer59DC2323": { + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3Bucket8D4A7808" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC" + } + ] + } + ] + } + ] + ] + } + }, + "Description": "/opt/awscli/aws" + } + }, + "ProductStackAssetBucket7d24e9c6ae26AssetsBucketDeploymentCustomResource2CE67D49": { + "Type": "Custom::CDKBucketDeployment", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", + "Arn" + ] + }, + "SourceBucketNames": [ + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3Bucket5D5509D9" + } + ], + "SourceObjectKeys": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080" + } + ] + } + ] + } + ] + ] + } + ], + "DestinationBucketName": { + "Ref": "ProductStackAssetBucket7d24e9c6ae26S3BucketD9992091" + }, + "UnzipFile": false, + "Prune": true + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "ProductStackAssetBucket7d24e9c6ae26S3BucketD9992091": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketName": "product-stack-asset-bucket-366234375154-2ff5213b8d39", + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, "TestProduct7606930B": { "Type": "AWS::ServiceCatalog::CloudFormationProduct", "Properties": { @@ -19,11 +367,7 @@ "Fn::Join": [ "", [ - "https://s3.", - { - "Ref": "AWS::Region" - }, - ".", + "https://s3.us-east-1.", { "Ref": "AWS::URLSuffix" }, @@ -70,11 +414,7 @@ "Fn::Join": [ "", [ - "https://s3.", - { - "Ref": "AWS::Region" - }, - ".", + "https://s3.us-east-1.", { "Ref": "AWS::URLSuffix" }, @@ -121,11 +461,7 @@ "Fn::Join": [ "", [ - "https://s3.", - { - "Ref": "AWS::Region" - }, - ".", + "https://s3.us-east-1.", { "Ref": "AWS::URLSuffix" }, @@ -172,11 +508,7 @@ "Fn::Join": [ "", [ - "https://s3.", - { - "Ref": "AWS::Region" - }, - ".", + "https://s3.us-east-1.", { "Ref": "AWS::URLSuffix" }, @@ -223,11 +555,7 @@ "Fn::Join": [ "", [ - "https://s3.", - { - "Ref": "AWS::Region" - }, - ".", + "https://s3.us-east-1.", { "Ref": "AWS::URLSuffix" }, @@ -327,6 +655,228 @@ "Value": "value1", "Active": true } + }, + "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:::", + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3Bucket5D5509D9" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":s3:::", + { + "Ref": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3Bucket5D5509D9" + } + ] + ] + } + ] + }, + { + "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": [ + "ProductStackAssetBucket7d24e9c6ae26S3BucketD9992091", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "ProductStackAssetBucket9033b902ec6bS3BucketAAEEA885", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "ProductStackAssetBucketd0571cd28fb8S3Bucket1257A20B", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ProductStackAssetBucket7d24e9c6ae26S3BucketD9992091", + "Arn" + ] + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ProductStackAssetBucket9033b902ec6bS3BucketAAEEA885", + "Arn" + ] + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "ProductStackAssetBucketd0571cd28fb8S3Bucket1257A20B", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", + "Roles": [ + { + "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" + } + ] + } + }, + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3Bucket5D7C192E" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3VersionKey9455D6FC" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3VersionKey9455D6FC" + } + ] + } + ] + } + ] + ] + } + }, + "Role": { + "Fn::GetAtt": [ + "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", + "Arn" + ] + }, + "Handler": "index.handler", + "Layers": [ + { + "Ref": "ProductStackAssetBucket9033b902ec6bAssetsBucketDeploymentAwsCliLayerB5D137F9" + } + ], + "Runtime": "python3.7", + "Timeout": 900 + } } }, "Parameters": { @@ -354,6 +904,42 @@ "Type": "String", "Description": "Artifact hash for asset \"6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5\"" }, + "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3Bucket8D4A7808": { + "Type": "String", + "Description": "S3 bucket for asset \"fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f\"" + }, + "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC": { + "Type": "String", + "Description": "S3 key for asset version \"fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f\"" + }, + "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fArtifactHashCC4FE319": { + "Type": "String", + "Description": "Artifact hash for asset \"fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f\"" + }, + "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3Bucket5D7C192E": { + "Type": "String", + "Description": "S3 bucket for asset \"f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6\"" + }, + "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3VersionKey9455D6FC": { + "Type": "String", + "Description": "S3 key for asset version \"f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6\"" + }, + "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6ArtifactHashF18E3990": { + "Type": "String", + "Description": "Artifact hash for asset \"f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6\"" + }, + "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3Bucket5D5509D9": { + "Type": "String", + "Description": "S3 bucket for asset \"95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df\"" + }, + "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080": { + "Type": "String", + "Description": "S3 key for asset version \"95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df\"" + }, + "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfArtifactHash44CE6B07": { + "Type": "String", + "Description": "Artifact hash for asset \"95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df\"" + }, "AssetParametersdd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1fS3BucketB4751C98": { "Type": "String", "Description": "S3 bucket for asset \"dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f\"" diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/manifest.json index c8d093eaf8a0c..5f0b100b23dee 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "18.0.0", + "version": "20.0.0", "artifacts": { "Tree": { "type": "cdk:tree", @@ -9,7 +9,7 @@ }, "integ-servicecatalog-product": { "type": "aws:cloudformation:stack", - "environment": "aws://unknown-account/unknown-region", + "environment": "aws://366234375154/us-east-1", "properties": { "templateFile": "integ-servicecatalog-product.template.json", "validateOnSynth": false @@ -40,6 +40,42 @@ "artifactHashParameter": "AssetParameters6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5ArtifactHashDC26AFAC" } }, + { + "type": "aws:cdk:asset", + "data": { + "path": "asset.fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f.zip", + "id": "fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f", + "packaging": "file", + "sourceHash": "fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f", + "s3BucketParameter": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3Bucket8D4A7808", + "s3KeyParameter": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC", + "artifactHashParameter": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fArtifactHashCC4FE319" + } + }, + { + "type": "aws:cdk:asset", + "data": { + "path": "asset.f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6", + "id": "f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6", + "packaging": "zip", + "sourceHash": "f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6", + "s3BucketParameter": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3Bucket5D7C192E", + "s3KeyParameter": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3VersionKey9455D6FC", + "artifactHashParameter": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6ArtifactHashF18E3990" + } + }, + { + "type": "aws:cdk:asset", + "data": { + "path": "asset.95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df.zip", + "id": "95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df", + "packaging": "file", + "sourceHash": "95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df", + "s3BucketParameter": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3Bucket5D5509D9", + "s3KeyParameter": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080", + "artifactHashParameter": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfArtifactHash44CE6B07" + } + }, { "type": "aws:cdk:asset", "data": { @@ -53,6 +89,60 @@ } } ], + "/integ-servicecatalog-product/ProductStackAssetBucket9033b902ec6b/AssetsBucketDeployment/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucket9033b902ec6bAssetsBucketDeploymentAwsCliLayerB5D137F9" + } + ], + "/integ-servicecatalog-product/ProductStackAssetBucket9033b902ec6b/AssetsBucketDeployment/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucket9033b902ec6bAssetsBucketDeploymentCustomResourceBB54446A" + } + ], + "/integ-servicecatalog-product/ProductStackAssetBucket9033b902ec6bS3Bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucket9033b902ec6bS3BucketAAEEA885" + } + ], + "/integ-servicecatalog-product/ProductStackAssetBucketd0571cd28fb8/AssetsBucketDeployment/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucketd0571cd28fb8AssetsBucketDeploymentAwsCliLayer450C5C9E" + } + ], + "/integ-servicecatalog-product/ProductStackAssetBucketd0571cd28fb8/AssetsBucketDeployment/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucketd0571cd28fb8AssetsBucketDeploymentCustomResourceE58C85B1" + } + ], + "/integ-servicecatalog-product/ProductStackAssetBucketd0571cd28fb8S3Bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucketd0571cd28fb8S3Bucket1257A20B" + } + ], + "/integ-servicecatalog-product/ProductStackAssetBucket7d24e9c6ae26/AssetsBucketDeployment/AwsCliLayer/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucket7d24e9c6ae26AssetsBucketDeploymentAwsCliLayer59DC2323" + } + ], + "/integ-servicecatalog-product/ProductStackAssetBucket7d24e9c6ae26/AssetsBucketDeployment/CustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucket7d24e9c6ae26AssetsBucketDeploymentCustomResource2CE67D49" + } + ], + "/integ-servicecatalog-product/ProductStackAssetBucket7d24e9c6ae26S3Bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProductStackAssetBucket7d24e9c6ae26S3BucketD9992091" + } + ], "/integ-servicecatalog-product/TestProduct/Resource": [ { "type": "aws:cdk:logicalId", @@ -113,6 +203,60 @@ "data": "AssetParameters6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5ArtifactHashDC26AFAC" } ], + "/integ-servicecatalog-product/AssetParameters/fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f/S3Bucket": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3Bucket8D4A7808" + } + ], + "/integ-servicecatalog-product/AssetParameters/fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f/S3VersionKey": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fS3VersionKeyC41B20EC" + } + ], + "/integ-servicecatalog-product/AssetParameters/fc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399f/ArtifactHash": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersfc2a3ca6356f84129511297b00cd5cef3c7b67abdf955eb5efa75c711da7399fArtifactHashCC4FE319" + } + ], + "/integ-servicecatalog-product/AssetParameters/f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6/S3Bucket": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3Bucket5D7C192E" + } + ], + "/integ-servicecatalog-product/AssetParameters/f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6/S3VersionKey": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6S3VersionKey9455D6FC" + } + ], + "/integ-servicecatalog-product/AssetParameters/f29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6/ArtifactHash": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParametersf29e9a05e35c4ad0889c1fb2d3f1daec35d8c568b4c69573160d5112c7b03cb6ArtifactHashF18E3990" + } + ], + "/integ-servicecatalog-product/AssetParameters/95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df/S3Bucket": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3Bucket5D5509D9" + } + ], + "/integ-servicecatalog-product/AssetParameters/95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df/S3VersionKey": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfS3VersionKeyF19FF080" + } + ], + "/integ-servicecatalog-product/AssetParameters/95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771df/ArtifactHash": [ + { + "type": "aws:cdk:logicalId", + "data": "AssetParameters95c924c84f5d023be4edee540cb2cb401a49f115d01ed403b288f6cb412771dfArtifactHash44CE6B07" + } + ], "/integ-servicecatalog-product/AssetParameters/dd2d087eeb6ede1d2a9166639ccbde7bd1b10eef9ba2b4cb3d9855faa4fe8c1f/S3Bucket": [ { "type": "aws:cdk:logicalId", @@ -148,6 +292,24 @@ "type": "aws:cdk:logicalId", "data": "TagOptionsa260cbbd99c416C40F73" } + ], + "/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" + } ] }, "displayName": "integ-servicecatalog-product" diff --git a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/tree.json b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/tree.json index 7916b0fe8b481..0ffc806884a02 100644 --- a/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/tree.json +++ b/packages/@aws-cdk/aws-servicecatalog/test/product.integ.snapshot/tree.json @@ -8,8 +8,8 @@ "id": "Tree", "path": "Tree", "constructInfo": { - "fqn": "@aws-cdk/core.Construct", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.65" } }, "integ-servicecatalog-product": { @@ -41,6 +41,32 @@ "fqn": "@aws-cdk/aws-sns.Topic", "version": "0.0.0" } + }, + "testAsset": { + "id": "testAsset", + "path": "integ-servicecatalog-product/SNSTopicProduct3/testAsset", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/SNSTopicProduct3/testAsset/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/SNSTopicProduct3/testAsset/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": { @@ -48,6 +74,44 @@ "version": "0.0.0" } }, + "ProductStackAssetBucket9033b902ec6b": { + "id": "ProductStackAssetBucket9033b902ec6b", + "path": "integ-servicecatalog-product/ProductStackAssetBucket9033b902ec6b", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.65" + } + }, + "ProductStackAssetBucket9033b902ec6bS3Bucket": { + "id": "ProductStackAssetBucket9033b902ec6bS3Bucket", + "path": "integ-servicecatalog-product/ProductStackAssetBucket9033b902ec6bS3Bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/ProductStackAssetBucket9033b902ec6bS3Bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketName": "product-stack-asset-bucket-366234375154-9d1b9ad6bf99", + "publicAccessBlockConfiguration": { + "blockPublicAcls": true, + "blockPublicPolicy": true, + "ignorePublicAcls": true, + "restrictPublicBuckets": true + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, "ProductStackHistory": { "id": "ProductStackHistory", "path": "integ-servicecatalog-product/ProductStackHistory", @@ -81,6 +145,32 @@ "fqn": "@aws-cdk/aws-sns.Topic", "version": "0.0.0" } + }, + "testAsset": { + "id": "testAsset", + "path": "integ-servicecatalog-product/SNSTopicProduct1/testAsset", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/SNSTopicProduct1/testAsset/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/SNSTopicProduct1/testAsset/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": { @@ -88,6 +178,44 @@ "version": "0.0.0" } }, + "ProductStackAssetBucketd0571cd28fb8": { + "id": "ProductStackAssetBucketd0571cd28fb8", + "path": "integ-servicecatalog-product/ProductStackAssetBucketd0571cd28fb8", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.65" + } + }, + "ProductStackAssetBucketd0571cd28fb8S3Bucket": { + "id": "ProductStackAssetBucketd0571cd28fb8S3Bucket", + "path": "integ-servicecatalog-product/ProductStackAssetBucketd0571cd28fb8S3Bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/ProductStackAssetBucketd0571cd28fb8S3Bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketName": "product-stack-asset-bucket-366234375154-9ffa45bce4c1", + "publicAccessBlockConfiguration": { + "blockPublicAcls": true, + "blockPublicPolicy": true, + "ignorePublicAcls": true, + "restrictPublicBuckets": true + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, "SNSTopicProduct2": { "id": "SNSTopicProduct2", "path": "integ-servicecatalog-product/SNSTopicProduct2", @@ -113,6 +241,32 @@ "fqn": "@aws-cdk/aws-sns.Topic", "version": "0.0.0" } + }, + "testAsset": { + "id": "testAsset", + "path": "integ-servicecatalog-product/SNSTopicProduct2/testAsset", + "children": { + "Stage": { + "id": "Stage", + "path": "integ-servicecatalog-product/SNSTopicProduct2/testAsset/Stage", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "integ-servicecatalog-product/SNSTopicProduct2/testAsset/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": { @@ -120,17 +274,55 @@ "version": "0.0.0" } }, + "ProductStackAssetBucket7d24e9c6ae26": { + "id": "ProductStackAssetBucket7d24e9c6ae26", + "path": "integ-servicecatalog-product/ProductStackAssetBucket7d24e9c6ae26", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.65" + } + }, + "ProductStackAssetBucket7d24e9c6ae26S3Bucket": { + "id": "ProductStackAssetBucket7d24e9c6ae26S3Bucket", + "path": "integ-servicecatalog-product/ProductStackAssetBucket7d24e9c6ae26S3Bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-servicecatalog-product/ProductStackAssetBucket7d24e9c6ae26S3Bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketName": "product-stack-asset-bucket-366234375154-2ff5213b8d39", + "publicAccessBlockConfiguration": { + "blockPublicAcls": true, + "blockPublicPolicy": true, + "ignorePublicAcls": true, + "restrictPublicBuckets": true + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, "TestProduct": { "id": "TestProduct", "path": "integ-servicecatalog-product/TestProduct", "children": { - "Template70cd971a7303": { - "id": "Template70cd971a7303", - "path": "integ-servicecatalog-product/TestProduct/Template70cd971a7303", + "Template84df734561eb": { + "id": "Template84df734561eb", + "path": "integ-servicecatalog-product/TestProduct/Template84df734561eb", "children": { "Stage": { "id": "Stage", - "path": "integ-servicecatalog-product/TestProduct/Template70cd971a7303/Stage", + "path": "integ-servicecatalog-product/TestProduct/Template84df734561eb/Stage", "constructInfo": { "fqn": "@aws-cdk/core.AssetStaging", "version": "0.0.0" @@ -138,7 +330,7 @@ }, "AssetBucket": { "id": "AssetBucket", - "path": "integ-servicecatalog-product/TestProduct/Template70cd971a7303/AssetBucket", + "path": "integ-servicecatalog-product/TestProduct/Template84df734561eb/AssetBucket", "constructInfo": { "fqn": "@aws-cdk/aws-s3.BucketBase", "version": "0.0.0" @@ -150,13 +342,13 @@ "version": "0.0.0" } }, - "Template3b1445e4244b": { - "id": "Template3b1445e4244b", - "path": "integ-servicecatalog-product/TestProduct/Template3b1445e4244b", + "Templatef0568392e7c0": { + "id": "Templatef0568392e7c0", + "path": "integ-servicecatalog-product/TestProduct/Templatef0568392e7c0", "children": { "Stage": { "id": "Stage", - "path": "integ-servicecatalog-product/TestProduct/Template3b1445e4244b/Stage", + "path": "integ-servicecatalog-product/TestProduct/Templatef0568392e7c0/Stage", "constructInfo": { "fqn": "@aws-cdk/core.AssetStaging", "version": "0.0.0" @@ -164,7 +356,7 @@ }, "AssetBucket": { "id": "AssetBucket", - "path": "integ-servicecatalog-product/TestProduct/Template3b1445e4244b/AssetBucket", + "path": "integ-servicecatalog-product/TestProduct/Templatef0568392e7c0/AssetBucket", "constructInfo": { "fqn": "@aws-cdk/aws-s3.BucketBase", "version": "0.0.0" @@ -198,11 +390,7 @@ "Fn::Join": [ "", [ - "https://s3.", - { - "Ref": "AWS::Region" - }, - ".", + "https://s3.us-east-1.", { "Ref": "AWS::URLSuffix" }, @@ -249,11 +437,7 @@ "Fn::Join": [ "", [ - "https://s3.", - { - "Ref": "AWS::Region" - }, - ".", + "https://s3.us-east-1.", { "Ref": "AWS::URLSuffix" }, @@ -411,8 +595,8 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/core.Construct", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.65" } }, "6412a5f4524c6b41d26fbeee226c68c2dad735393940a51008d77e6f8b1038f5": { @@ -445,14 +629,14 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/core.Construct", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.65" } } }, "constructInfo": { - "fqn": "@aws-cdk/core.Construct", - "version": "0.0.0" + "fqn": "constructs.Construct", + "version": "10.1.65" } }, "TagOptions": { diff --git a/packages/@aws-cdk/aws-servicecatalog/test/products.template.zip b/packages/@aws-cdk/aws-servicecatalog/test/products.template.zip new file mode 100644 index 0000000000000..082d24089e5ff Binary files /dev/null and b/packages/@aws-cdk/aws-servicecatalog/test/products.template.zip differ