Skip to content

Commit 60acc21

Browse files
authored
Merge branch 'master' into bug(11221)-NestedStack-defaultChild
2 parents 6464a6d + 1037b8c commit 60acc21

40 files changed

Lines changed: 1210 additions & 417 deletions

File tree

packages/@aws-cdk/aws-lambda-destinations/test/integ.destinations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ const integ = new IntegTest(app, 'Destinations', {
7373
testCases: [stack],
7474
});
7575

76-
integ.assert.invokeFunction({
76+
integ.assertions.invokeFunction({
7777
functionName: stack.fn.functionName,
7878
invocationType: InvocationType.EVENT,
7979
payload: JSON.stringify({ status: 'OK' }),
8080
});
8181

82-
const message = integ.assert.awsApiCall('SQS', 'receiveMessage', {
82+
const message = integ.assertions.awsApiCall('SQS', 'receiveMessage', {
8383
QueueUrl: stack.queue.queueUrl,
8484
WaitTimeSeconds: 20,
8585
});

packages/@aws-cdk/aws-lambda/test/integ.bundling.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ const integ = new IntegTest(app, 'Bundling', {
4747
stackUpdateWorkflow: false,
4848
});
4949

50-
const invoke = integ.assert.invokeFunction({
50+
const invoke = integ.assertions.invokeFunction({
5151
functionName: stack.functionName,
5252
});
53-
invoke.assert(ExpectedResult.objectLike({
53+
invoke.expect(ExpectedResult.objectLike({
5454
Payload: '200',
5555
}));
5656
app.synth();

packages/@aws-cdk/aws-s3/README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -522,15 +522,15 @@ by deploying with CDK version `1.126.0` or later **before** switching this value
522522

523523
```ts
524524
const bucket = new s3.Bucket(this, 'MyBucket', {
525-
transferAcceleration: true,
525+
transferAcceleration: true,
526526
});
527527
```
528528

529529
To access the bucket that is enabled for Transfer Acceleration, you must use a special endpoint. The URL can be generated using method `transferAccelerationUrlForObject`:
530530

531531
```ts
532532
const bucket = new s3.Bucket(this, 'MyBucket', {
533-
transferAcceleration: true,
533+
transferAcceleration: true,
534534
});
535535
bucket.transferAccelerationUrlForObject('objectname');
536536
```
@@ -540,14 +540,14 @@ bucket.transferAccelerationUrlForObject('objectname');
540540
[Intelligent Tiering](https://docs.aws.amazon.com/AmazonS3/latest/userguide/intelligent-tiering.html) can be configured to automatically move files to glacier:
541541

542542
```ts
543-
new s3.Bucket(this, 'MyBucket', {
544-
intelligentTieringConfigurations: [{
545-
name: 'foo',
546-
prefix: 'folder/name',
547-
archiveAccessTierTime: cdk.Duration.days(90),
548-
deepArchiveAccessTierTime: cdk.Duration.days(180),
549-
tags: [{key: 'tagname', value: 'tagvalue'}]
550-
}],
543+
new s3.Bucket(this, 'MyBucket', {
544+
intelligentTieringConfigurations: [{
545+
name: 'foo',
546+
prefix: 'folder/name',
547+
archiveAccessTierTime: cdk.Duration.days(90),
548+
deepArchiveAccessTierTime: cdk.Duration.days(180),
549+
tags: [{key: 'tagname', value: 'tagvalue'}]
550+
}],
551551
});
552552

553553
```

packages/@aws-cdk/aws-servicecatalog/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*.d.ts
44
tsconfig.json
55
node_modules
6+
product-stack-snapshots
67
*.generated.ts
78
dist
89
.jsii

packages/@aws-cdk/aws-servicecatalog/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ enables organizations to create and manage catalogs of products for their end us
2222
- [Product](#product)
2323
- [Creating a product from a local asset](#creating-a-product-from-local-asset)
2424
- [Creating a product from a stack](#creating-a-product-from-a-stack)
25+
- [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)
2526
- [Adding a product to a portfolio](#adding-a-product-to-a-portfolio)
2627
- [TagOptions](#tag-options)
2728
- [Constraints](#constraints)
@@ -184,6 +185,105 @@ const product = new servicecatalog.CloudFormationProduct(this, 'Product', {
184185
});
185186
```
186187

188+
### Creating a Product from a stack with a history of previous versions
189+
190+
The default behavior of Service Catalog is to overwrite each product version upon deployment.
191+
This applies to Product Stacks as well, where only the latest changes to your Product Stack will
192+
be deployed.
193+
To keep a history of the revisions of a ProductStack available in Service Catalog,
194+
you would need to define a ProductStack for each historical copy.
195+
196+
You can instead create a `ProductStackHistory` to maintain snapshots of all previous versions.
197+
The `ProductStackHistory` can be created by passing the base `productStack`,
198+
a `currentVersionName` for your current version and a `locked` boolean.
199+
The `locked` boolean which when set to true will prevent your `currentVersionName`
200+
from being overwritten when there is an existing snapshot for that version.
201+
202+
```ts
203+
import * as s3 from '@aws-cdk/aws-s3';
204+
import * as cdk from '@aws-cdk/core';
205+
206+
class S3BucketProduct extends servicecatalog.ProductStack {
207+
constructor(scope: cdk.Construct, id: string) {
208+
super(scope, id);
209+
210+
new s3.Bucket(this, 'BucketProduct');
211+
}
212+
}
213+
214+
const productStackHistory = new servicecatalog.ProductStackHistory(this, 'ProductStackHistory', {
215+
productStack: new S3BucketProduct(this, 'S3BucketProduct'),
216+
currentVersionName: 'v1',
217+
currentVersionLocked: true
218+
});
219+
```
220+
221+
We can deploy the current version `v1` by using `productStackHistory.currentVersion()`
222+
223+
```ts
224+
import * as s3 from '@aws-cdk/aws-s3';
225+
import * as cdk from '@aws-cdk/core';
226+
227+
class S3BucketProduct extends servicecatalog.ProductStack {
228+
constructor(scope: cdk.Construct, id: string) {
229+
super(scope, id);
230+
231+
new s3.Bucket(this, 'BucketProductV2');
232+
}
233+
}
234+
235+
const productStackHistory = new servicecatalog.ProductStackHistory(this, 'ProductStackHistory', {
236+
productStack: new S3BucketProduct(this, 'S3BucketProduct'),
237+
currentVersionName: 'v2',
238+
currentVersionLocked: true
239+
});
240+
241+
const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', {
242+
productName: "My Product",
243+
owner: "Product Owner",
244+
productVersions: [
245+
productStackHistory.currentVersion(),
246+
],
247+
});
248+
```
249+
250+
Using `ProductStackHistory` all deployed templates for the ProductStack will be written to disk,
251+
so that they will still be available in the future as the definition of the `ProductStack` subclass changes over time.
252+
**It is very important** that you commit these old versions to source control as these versions
253+
determine whether a version has already been deployed and can also be deployed themselves.
254+
255+
After using `ProductStackHistory` to deploy version `v1` of your `ProductStack`, we
256+
make changes to the `ProductStack` and update the `currentVersionName` to `v2`.
257+
We still want our `v1` version to still be deployed, so we reference it by calling `productStackHistory.versionFromSnapshot('v1')`.
258+
259+
```ts
260+
import * as s3 from '@aws-cdk/aws-s3';
261+
import * as cdk from '@aws-cdk/core';
262+
263+
class S3BucketProduct extends servicecatalog.ProductStack {
264+
constructor(scope: cdk.Construct, id: string) {
265+
super(scope, id);
266+
267+
new s3.Bucket(this, 'BucketProductV2');
268+
}
269+
}
270+
271+
const productStackHistory = new servicecatalog.ProductStackHistory(this, 'ProductStackHistory', {
272+
productStack: new S3BucketProduct(this, 'S3BucketProduct'),
273+
currentVersionName: 'v2',
274+
currentVersionLocked: true
275+
});
276+
277+
const product = new servicecatalog.CloudFormationProduct(this, 'MyFirstProduct', {
278+
productName: "My Product",
279+
owner: "Product Owner",
280+
productVersions: [
281+
productStackHistory.currentVersion(),
282+
productStackHistory.versionFromSnapshot('v1')
283+
],
284+
});
285+
```
286+
187287
### Adding a product to a portfolio
188288

189289
You add products to a portfolio to organize and distribute your catalog at scale. Adding a product to a portfolio creates an association,

packages/@aws-cdk/aws-servicecatalog/lib/cloudformation-template.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ class CloudFormationAssetTemplate extends CloudFormationTemplate {
102102
*/
103103
class CloudFormationProductStackTemplate extends CloudFormationTemplate {
104104
/**
105-
* @param stack A service catalog product stack.
106-
*/
105+
* @param productStack A service catalog product stack.
106+
*/
107107
constructor(public readonly productStack: ProductStack) {
108108
super();
109109
}

packages/@aws-cdk/aws-servicecatalog/lib/common.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Constant for the default directory to store ProductStack snapshots.
3+
*/
4+
export const DEFAULT_PRODUCT_STACK_SNAPSHOT_DIRECTORY = 'product-stack-snapshots';
5+
16
/**
27
* The language code.
38
* Used for error and logging messages for end users.
@@ -18,4 +23,4 @@ export enum MessageLanguage {
1823
* Chinese
1924
*/
2025
ZH = 'zh'
21-
}
26+
}

packages/@aws-cdk/aws-servicecatalog/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from './cloudformation-template';
44
export * from './portfolio';
55
export * from './product';
66
export * from './product-stack';
7+
export * from './product-stack-history';
78
export * from './tag-options';
89

910
// AWS::ServiceCatalog CloudFormation Resources:
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import { Names } from '@aws-cdk/core';
4+
import { Construct } from 'constructs';
5+
import { CloudFormationTemplate } from './cloudformation-template';
6+
import { DEFAULT_PRODUCT_STACK_SNAPSHOT_DIRECTORY } from './common';
7+
import { CloudFormationProductVersion } from './product';
8+
import { ProductStack } from './product-stack';
9+
10+
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
11+
// eslint-disable-next-line no-duplicate-imports, import/order
12+
import { Construct as CoreConstruct } from '@aws-cdk/core';
13+
14+
/**
15+
* Properties for a ProductStackHistory.
16+
*/
17+
export interface ProductStackHistoryProps {
18+
/**
19+
* The ProductStack whose history will be retained as a snapshot
20+
*/
21+
readonly productStack: ProductStack;
22+
23+
/**
24+
* The current version name of the ProductStack.
25+
*/
26+
readonly currentVersionName: string;
27+
28+
/**
29+
* If this is set to true, the ProductStack will not be overwritten if a snapshot is found for the currentVersionName.
30+
*/
31+
readonly currentVersionLocked: boolean
32+
33+
/**
34+
* The description of the product version
35+
* @default - No description provided
36+
*/
37+
readonly description?: string;
38+
39+
/**
40+
* Whether the specified product template will be validated by CloudFormation.
41+
* If turned off, an invalid template configuration can be stored.
42+
* @default true
43+
*/
44+
readonly validateTemplate?: boolean;
45+
46+
/**
47+
* The directory where template snapshots will be stored
48+
* @default 'product-stack-snapshots'
49+
*/
50+
readonly directory?: string
51+
}
52+
53+
/**
54+
* A Construct that contains a Service Catalog product stack with its previous deployments maintained.
55+
*/
56+
export class ProductStackHistory extends CoreConstruct {
57+
private readonly props: ProductStackHistoryProps
58+
constructor(scope: Construct, id: string, props: ProductStackHistoryProps) {
59+
super(scope, id);
60+
props.productStack._setParentProductStackHistory(this);
61+
this.props = props;
62+
}
63+
64+
/**
65+
* Retains product stack template as a snapshot when deployed and
66+
* retrieves a CloudFormationProductVersion for the current product version.
67+
*/
68+
public currentVersion() : CloudFormationProductVersion {
69+
return {
70+
cloudFormationTemplate: CloudFormationTemplate.fromProductStack(this.props.productStack),
71+
productVersionName: this.props.currentVersionName,
72+
description: this.props.description,
73+
};
74+
}
75+
76+
/**
77+
* Retrieves a CloudFormationProductVersion from a previously deployed productVersionName.
78+
*/
79+
public versionFromSnapshot(productVersionName: string) : CloudFormationProductVersion {
80+
const productStackSnapshotDirectory = this.props.directory || DEFAULT_PRODUCT_STACK_SNAPSHOT_DIRECTORY;
81+
const templateFileKey = `${Names.uniqueId(this)}.${this.props.productStack.artifactId}.${productVersionName}.product.template.json`;
82+
const templateFilePath = path.join(productStackSnapshotDirectory, templateFileKey);
83+
if (!fs.existsSync(templateFilePath)) {
84+
throw new Error(`Template ${templateFileKey} cannot be found in ${productStackSnapshotDirectory}`);
85+
}
86+
return {
87+
cloudFormationTemplate: CloudFormationTemplate.fromAsset(templateFilePath),
88+
productVersionName: productVersionName,
89+
description: this.props.description,
90+
};
91+
}
92+
93+
/**
94+
* Writes current template generated from Product Stack to a snapshot directory.
95+
*
96+
* @internal
97+
*/
98+
public _writeTemplateToSnapshot(cfn: string) {
99+
const productStackSnapshotDirectory = this.props.directory || DEFAULT_PRODUCT_STACK_SNAPSHOT_DIRECTORY;
100+
if (!fs.existsSync(productStackSnapshotDirectory)) {
101+
fs.mkdirSync(productStackSnapshotDirectory);
102+
}
103+
const templateFileKey = `${Names.uniqueId(this)}.${this.props.productStack.artifactId}.${this.props.currentVersionName}.product.template.json`;
104+
const templateFilePath = path.join(productStackSnapshotDirectory, templateFileKey);
105+
if (fs.existsSync(templateFilePath)) {
106+
const previousCfn = fs.readFileSync(templateFilePath).toString();
107+
if (previousCfn !== cfn && this.props.currentVersionLocked) {
108+
throw new Error(`Template has changed for ProductStack Version ${this.props.currentVersionName}.
109+
${this.props.currentVersionName} already exist in ${productStackSnapshotDirectory}.
110+
Since locked has been set to ${this.props.currentVersionLocked},
111+
Either update the currentVersionName to deploy a new version or deploy the existing ProductStack snapshot.
112+
If ${this.props.currentVersionName} was unintentionally synthesized and not deployed,
113+
delete the corresponding version from ${productStackSnapshotDirectory} and redeploy.`);
114+
}
115+
}
116+
fs.writeFileSync(templateFilePath, cfn);
117+
}
118+
}

packages/@aws-cdk/aws-servicecatalog/lib/product-stack.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as fs from 'fs';
33
import * as path from 'path';
44
import * as cdk from '@aws-cdk/core';
55
import { ProductStackSynthesizer } from './private/product-stack-synthesizer';
6+
import { ProductStackHistory } from './product-stack-history';
67

78
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
89
// eslint-disable-next-line no-duplicate-imports, import/order
@@ -19,6 +20,7 @@ import { Construct } from 'constructs';
1920
*/
2021
export class ProductStack extends cdk.Stack {
2122
public readonly templateFile: string;
23+
private _parentProductStackHistory?: ProductStackHistory;
2224
private _templateUrl?: string;
2325
private _parentStack: cdk.Stack;
2426

@@ -33,6 +35,15 @@ export class ProductStack extends cdk.Stack {
3335
this.templateFile = `${cdk.Names.uniqueId(this)}.product.template.json`;
3436
}
3537

38+
/**
39+
* Set the parent product stack history
40+
*
41+
* @internal
42+
*/
43+
public _setParentProductStackHistory(parentProductStackHistory: ProductStackHistory) {
44+
return this._parentProductStackHistory = parentProductStackHistory;
45+
}
46+
3647
/**
3748
* Fetch the template URL.
3849
*
@@ -60,6 +71,10 @@ export class ProductStack extends cdk.Stack {
6071
fileName: this.templateFile,
6172
}).httpUrl;
6273

74+
if (this._parentProductStackHistory) {
75+
this._parentProductStackHistory._writeTemplateToSnapshot(cfn);
76+
}
77+
6378
fs.writeFileSync(path.join(session.assembly.outdir, this.templateFile), cfn);
6479
}
6580
}

0 commit comments

Comments
 (0)