-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
product-stack.ts
148 lines (130 loc) · 4.46 KB
/
product-stack.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import { IBucket } from '@aws-cdk/aws-s3';
import { BucketDeployment, ISource, Source } from '@aws-cdk/aws-s3-deployment';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';
import { ProductStackSynthesizer } from './private/product-stack-synthesizer';
import { ProductStackHistory } from './product-stack-history';
/**
* Product stack props.
*/
export interface ProductStackProps {
/**
* A Bucket can be passed to store assets, enabling ProductStack Asset support
* @default No Bucket provided and Assets will not be supported.
*/
readonly assetBucket?: IBucket;
}
/**
* A Service Catalog product stack, which is similar in form to a Cloudformation nested stack.
* You can add the resources to this stack that you want to define for your service catalog product.
*
* This stack will not be treated as an independent deployment
* artifact (won't be listed in "cdk list" or deployable through "cdk deploy"),
* but rather only synthesized as a template and uploaded as an asset to S3.
*
*/
export class ProductStack extends cdk.Stack {
public readonly templateFile: string;
private _parentProductStackHistory?: ProductStackHistory;
private _templateUrl?: string;
private _parentStack: cdk.Stack;
private readonly assets: ISource[];
private assetBucket?: IBucket;
constructor(scope: Construct, id: string, props: ProductStackProps = {}) {
super(scope, id, {
synthesizer: new ProductStackSynthesizer(props.assetBucket),
});
this._parentStack = findParentStack(scope);
// this is the file name of the synthesized template file within the cloud assembly
this.templateFile = `${cdk.Names.uniqueId(this)}.product.template.json`;
this.assets = [];
this.assetBucket = props.assetBucket;
}
/**
* Set the parent product stack history
*
* @internal
*/
public _setParentProductStackHistory(parentProductStackHistory: ProductStackHistory) {
return this._parentProductStackHistory = parentProductStackHistory;
}
/**
* Fetch the template URL.
*
* @internal
*/
public _getTemplateUrl(): string {
return cdk.Lazy.uncachedString({ produce: () => this._templateUrl });
}
/**
* Fetch the asset bucket.
*
* @internal
*/
public _getAssetBucket(): IBucket | undefined {
return this.assetBucket;
}
/**
* Asset are prepared for bulk deployment to S3.
* @internal
*/
public _addAsset(asset: cdk.FileAssetSource): void {
const assetPath = './cdk.out/' + asset.fileName;
this.assets.push(Source.asset(assetPath));
}
/**
* Deploy all assets to S3.
* @internal
*/
private _deployAssets() {
if (this.assetBucket && this.assets.length > 0) {
if (!cdk.Resource.isOwnedResource(this.assetBucket)) {
// eslint-disable-next-line no-console
console.warn('[WARNING]', 'Bucket Policy Permissions cannot be added to' +
' referenced Bucket. Please make sure your bucket has the correct permissions');
}
new BucketDeployment(this._parentStack, 'AssetsBucketDeployment', {
sources: this.assets,
destinationBucket: this.assetBucket,
extract: false,
prune: false,
});
}
}
/**
* Synthesize the product stack template, overrides the `super` class method.
*
* Defines an asset at the parent stack which represents the template of this
* product stack.
*
* @internal
*/
public _synthesizeTemplate(session: cdk.ISynthesisSession): void {
const cfn = JSON.stringify(this._toCloudFormation(), undefined, 2);
const templateHash = crypto.createHash('sha256').update(cfn).digest('hex');
this._templateUrl = this._parentStack.synthesizer.addFileAsset({
packaging: cdk.FileAssetPackaging.FILE,
sourceHash: templateHash,
fileName: this.templateFile,
}).httpUrl;
if (this._parentProductStackHistory) {
this._parentProductStackHistory._writeTemplateToSnapshot(cfn);
}
this._deployAssets();
fs.writeFileSync(path.join(session.assembly.outdir, this.templateFile), cfn);
}
}
/**
* Validates the scope for a product stack, which must be defined within the scope of another `Stack`.
*/
function findParentStack(scope: Construct): cdk.Stack {
try {
const parentStack = cdk.Stack.of(scope);
return parentStack as cdk.Stack;
} catch (e) {
throw new Error('Product stacks must be defined within scope of another non-product stack');
}
}