@dga-itc/aws-cdk-constructs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -0
- package/dist/aws-cdk/constructs/acm.d.ts +28 -0
- package/dist/aws-cdk/constructs/acm.js +239 -0
- package/dist/aws-cdk/constructs/alb.d.ts +28 -0
- package/dist/aws-cdk/constructs/alb.js +304 -0
- package/dist/aws-cdk/constructs/bastion.d.ts +46 -0
- package/dist/aws-cdk/constructs/bastion.js +332 -0
- package/dist/aws-cdk/constructs/cloudfront.d.ts +45 -0
- package/dist/aws-cdk/constructs/cloudfront.js +261 -0
- package/dist/aws-cdk/constructs/ecr.d.ts +17 -0
- package/dist/aws-cdk/constructs/ecr.js +143 -0
- package/dist/aws-cdk/constructs/ecs-cluster.d.ts +21 -0
- package/dist/aws-cdk/constructs/ecs-cluster.js +124 -0
- package/dist/aws-cdk/constructs/ecs-service.d.ts +72 -0
- package/dist/aws-cdk/constructs/ecs-service.js +682 -0
- package/dist/aws-cdk/constructs/efs.d.ts +31 -0
- package/dist/aws-cdk/constructs/efs.js +241 -0
- package/dist/aws-cdk/constructs/elasticache.d.ts +35 -0
- package/dist/aws-cdk/constructs/elasticache.js +210 -0
- package/dist/aws-cdk/constructs/nacl.d.ts +37 -0
- package/dist/aws-cdk/constructs/nacl.js +88 -0
- package/dist/aws-cdk/constructs/nlb.d.ts +39 -0
- package/dist/aws-cdk/constructs/nlb.js +276 -0
- package/dist/aws-cdk/constructs/rds.d.ts +40 -0
- package/dist/aws-cdk/constructs/rds.js +320 -0
- package/dist/aws-cdk/constructs/self-signed-cert.d.ts +83 -0
- package/dist/aws-cdk/constructs/self-signed-cert.js +215 -0
- package/dist/aws-cdk/constructs/sqs.d.ts +30 -0
- package/dist/aws-cdk/constructs/sqs.js +268 -0
- package/dist/aws-cdk/constructs/vpc.d.ts +30 -0
- package/dist/aws-cdk/constructs/vpc.js +423 -0
- package/dist/aws-cdk/constructs/waf.d.ts +37 -0
- package/dist/aws-cdk/constructs/waf.js +350 -0
- package/dist/aws-cdk/interfaces/account-config.d.ts +18 -0
- package/dist/aws-cdk/interfaces/account-config.js +2 -0
- package/dist/aws-cdk/interfaces/acm-config.d.ts +94 -0
- package/dist/aws-cdk/interfaces/acm-config.js +14 -0
- package/dist/aws-cdk/interfaces/alb-config.d.ts +72 -0
- package/dist/aws-cdk/interfaces/alb-config.js +2 -0
- package/dist/aws-cdk/interfaces/bastion-config.d.ts +77 -0
- package/dist/aws-cdk/interfaces/bastion-config.js +10 -0
- package/dist/aws-cdk/interfaces/cloudfront-config.d.ts +154 -0
- package/dist/aws-cdk/interfaces/cloudfront-config.js +15 -0
- package/dist/aws-cdk/interfaces/ecr-config.d.ts +40 -0
- package/dist/aws-cdk/interfaces/ecr-config.js +2 -0
- package/dist/aws-cdk/interfaces/ecs-cluster-config.d.ts +30 -0
- package/dist/aws-cdk/interfaces/ecs-cluster-config.js +2 -0
- package/dist/aws-cdk/interfaces/ecs-service-config.d.ts +237 -0
- package/dist/aws-cdk/interfaces/ecs-service-config.js +2 -0
- package/dist/aws-cdk/interfaces/efs-config.d.ts +56 -0
- package/dist/aws-cdk/interfaces/efs-config.js +7 -0
- package/dist/aws-cdk/interfaces/elasticache-config.d.ts +56 -0
- package/dist/aws-cdk/interfaces/elasticache-config.js +7 -0
- package/dist/aws-cdk/interfaces/nacl-config.d.ts +1 -0
- package/dist/aws-cdk/interfaces/nacl-config.js +3 -0
- package/dist/aws-cdk/interfaces/nlb-config.d.ts +69 -0
- package/dist/aws-cdk/interfaces/nlb-config.js +2 -0
- package/dist/aws-cdk/interfaces/rds-config.d.ts +84 -0
- package/dist/aws-cdk/interfaces/rds-config.js +7 -0
- package/dist/aws-cdk/interfaces/sqs-config.d.ts +145 -0
- package/dist/aws-cdk/interfaces/sqs-config.js +12 -0
- package/dist/aws-cdk/interfaces/tag-config.d.ts +18 -0
- package/dist/aws-cdk/interfaces/tag-config.js +2 -0
- package/dist/aws-cdk/interfaces/vpc-config.d.ts +72 -0
- package/dist/aws-cdk/interfaces/vpc-config.js +2 -0
- package/dist/aws-cdk/interfaces/waf-config.d.ts +180 -0
- package/dist/aws-cdk/interfaces/waf-config.js +2 -0
- package/dist/aws-cdk/utils/priority-tracker.d.ts +60 -0
- package/dist/aws-cdk/utils/priority-tracker.js +131 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +55 -0
- package/dist/terraform-cdk/constructs/alb-listener-rule.d.ts +33 -0
- package/dist/terraform-cdk/constructs/alb-listener-rule.js +81 -0
- package/dist/terraform-cdk/constructs/ecs-service.d.ts +29 -0
- package/dist/terraform-cdk/constructs/ecs-service.js +238 -0
- package/dist/terraform-cdk/interfaces/ecs-service-config.d.ts +53 -0
- package/dist/terraform-cdk/interfaces/ecs-service-config.js +25 -0
- package/dist/terraform-cdk/interfaces/infrastructure-refs.d.ts +16 -0
- package/dist/terraform-cdk/interfaces/infrastructure-refs.js +8 -0
- package/dist/terraform-cdk/utils/priority-tracker.d.ts +60 -0
- package/dist/terraform-cdk/utils/priority-tracker.js +131 -0
- package/package.json +46 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
3
|
+
import * as efs from 'aws-cdk-lib/aws-efs';
|
|
4
|
+
import { EfsConfig } from '../interfaces/efs-config';
|
|
5
|
+
import { VpcConstruct } from './vpc';
|
|
6
|
+
export interface EfsConstructProps {
|
|
7
|
+
config: EfsConfig;
|
|
8
|
+
/** VpcConstruct from NetworkStack */
|
|
9
|
+
vpcConstruct: VpcConstruct;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* EFS Construct - สร้าง Elastic File System + Mount Targets + Access Points
|
|
13
|
+
*
|
|
14
|
+
* ใช้สำหรับ persistent storage ของ ECS Fargate tasks
|
|
15
|
+
*/
|
|
16
|
+
export declare class EfsConstruct extends Construct {
|
|
17
|
+
readonly fileSystem: efs.FileSystem;
|
|
18
|
+
readonly securityGroup: ec2.SecurityGroup;
|
|
19
|
+
readonly accessPoints: Map<string, efs.AccessPoint>;
|
|
20
|
+
private readonly removalPolicy;
|
|
21
|
+
constructor(scope: Construct, id: string, props: EfsConstructProps);
|
|
22
|
+
private resolveVpc;
|
|
23
|
+
private createSecurityGroup;
|
|
24
|
+
private createFileSystem;
|
|
25
|
+
private createAccessPoints;
|
|
26
|
+
private createOutputs;
|
|
27
|
+
/**
|
|
28
|
+
* Allow NFS access from a security group
|
|
29
|
+
*/
|
|
30
|
+
allowNfsFrom(sg: ec2.ISecurityGroup, description?: string): void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EfsConstruct = void 0;
|
|
37
|
+
const constructs_1 = require("constructs");
|
|
38
|
+
const cdk = __importStar(require("aws-cdk-lib"));
|
|
39
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
40
|
+
const ec2 = __importStar(require("aws-cdk-lib/aws-ec2"));
|
|
41
|
+
const efs = __importStar(require("aws-cdk-lib/aws-efs"));
|
|
42
|
+
/**
|
|
43
|
+
* EFS Construct - สร้าง Elastic File System + Mount Targets + Access Points
|
|
44
|
+
*
|
|
45
|
+
* ใช้สำหรับ persistent storage ของ ECS Fargate tasks
|
|
46
|
+
*/
|
|
47
|
+
class EfsConstruct extends constructs_1.Construct {
|
|
48
|
+
constructor(scope, id, props) {
|
|
49
|
+
super(scope, id);
|
|
50
|
+
this.accessPoints = new Map();
|
|
51
|
+
const { config, vpcConstruct } = props;
|
|
52
|
+
this.removalPolicy = config.removalPolicy === 'destroy'
|
|
53
|
+
? aws_cdk_lib_1.RemovalPolicy.DESTROY
|
|
54
|
+
: aws_cdk_lib_1.RemovalPolicy.RETAIN;
|
|
55
|
+
// A. Resolve VPC & Subnets
|
|
56
|
+
const { vpc, subnets } = this.resolveVpc(config, vpcConstruct);
|
|
57
|
+
// B. Create Security Group
|
|
58
|
+
this.securityGroup = this.createSecurityGroup(vpc, config);
|
|
59
|
+
// C. Create EFS File System
|
|
60
|
+
this.fileSystem = this.createFileSystem(config, vpc, subnets);
|
|
61
|
+
// D. Create Access Points
|
|
62
|
+
this.createAccessPoints(config);
|
|
63
|
+
// E. Apply Removal Policy
|
|
64
|
+
this.fileSystem.applyRemovalPolicy(this.removalPolicy);
|
|
65
|
+
this.securityGroup.applyRemovalPolicy(this.removalPolicy);
|
|
66
|
+
// F. Outputs
|
|
67
|
+
this.createOutputs(config);
|
|
68
|
+
}
|
|
69
|
+
// ==========================================
|
|
70
|
+
// Resolve VPC
|
|
71
|
+
// ==========================================
|
|
72
|
+
resolveVpc(config, vpcConstruct) {
|
|
73
|
+
const subnetName = config.source.subnetName ?? 'private-ecs';
|
|
74
|
+
const cfnSubnets = vpcConstruct.subnetsByName.get(subnetName);
|
|
75
|
+
if (!cfnSubnets || cfnSubnets.length === 0) {
|
|
76
|
+
throw new Error(`No subnets found with name "${subnetName}" in VPC construct. ` +
|
|
77
|
+
`Available: ${Array.from(vpcConstruct.subnetsByName.keys()).join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
// Build subnets with routeTableId from VpcConstruct
|
|
80
|
+
const subnetIds = [];
|
|
81
|
+
const subnets = [];
|
|
82
|
+
const azs = [];
|
|
83
|
+
const routeTableIds = [];
|
|
84
|
+
vpcConstruct.subnets.forEach((cfnSubnet, subnetKey) => {
|
|
85
|
+
if (subnetKey.startsWith(`${subnetName}-`)) {
|
|
86
|
+
const az = subnetKey.replace(`${subnetName}-`, '');
|
|
87
|
+
const routeTableId = vpcConstruct.subnetRouteTableMap.get(subnetKey);
|
|
88
|
+
subnetIds.push(cfnSubnet.ref);
|
|
89
|
+
azs.push(cfnSubnet.availabilityZone);
|
|
90
|
+
if (routeTableId)
|
|
91
|
+
routeTableIds.push(routeTableId);
|
|
92
|
+
subnets.push(ec2.Subnet.fromSubnetAttributes(this, `Subnet-${az}`, {
|
|
93
|
+
subnetId: cfnSubnet.ref,
|
|
94
|
+
availabilityZone: cfnSubnet.availabilityZone,
|
|
95
|
+
routeTableId,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
const hasRtIds = routeTableIds.length === subnetIds.length;
|
|
100
|
+
const vpc = ec2.Vpc.fromVpcAttributes(this, 'Vpc', {
|
|
101
|
+
vpcId: vpcConstruct.vpc.ref,
|
|
102
|
+
availabilityZones: azs,
|
|
103
|
+
privateSubnetIds: subnetIds,
|
|
104
|
+
privateSubnetRouteTableIds: hasRtIds ? routeTableIds : undefined,
|
|
105
|
+
});
|
|
106
|
+
return { vpc, subnets };
|
|
107
|
+
}
|
|
108
|
+
// ==========================================
|
|
109
|
+
// Security Group
|
|
110
|
+
// ==========================================
|
|
111
|
+
createSecurityGroup(vpc, config) {
|
|
112
|
+
const sg = new ec2.SecurityGroup(this, 'EfsSecurityGroup', {
|
|
113
|
+
vpc,
|
|
114
|
+
securityGroupName: config.securityGroupName,
|
|
115
|
+
description: `Security group for ${config.fileSystemName} EFS`,
|
|
116
|
+
allowAllOutbound: false, // EFS doesn't need outbound
|
|
117
|
+
});
|
|
118
|
+
// EFS listens on port 2049 (NFS)
|
|
119
|
+
// Ingress rules will be added by ECS service construct
|
|
120
|
+
return sg;
|
|
121
|
+
}
|
|
122
|
+
// ==========================================
|
|
123
|
+
// Create EFS File System
|
|
124
|
+
// ==========================================
|
|
125
|
+
createFileSystem(config, vpc, subnets) {
|
|
126
|
+
const performanceMode = config.performanceMode === 'maxIO'
|
|
127
|
+
? efs.PerformanceMode.MAX_IO
|
|
128
|
+
: efs.PerformanceMode.GENERAL_PURPOSE;
|
|
129
|
+
let throughputMode;
|
|
130
|
+
switch (config.throughputMode) {
|
|
131
|
+
case 'provisioned':
|
|
132
|
+
throughputMode = efs.ThroughputMode.PROVISIONED;
|
|
133
|
+
break;
|
|
134
|
+
case 'elastic':
|
|
135
|
+
throughputMode = efs.ThroughputMode.ELASTIC;
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
throughputMode = efs.ThroughputMode.BURSTING;
|
|
139
|
+
}
|
|
140
|
+
const transitionDays = config.transitionToIaAfterDays ?? 30;
|
|
141
|
+
let lifecyclePolicy;
|
|
142
|
+
switch (transitionDays) {
|
|
143
|
+
case 7:
|
|
144
|
+
lifecyclePolicy = efs.LifecyclePolicy.AFTER_7_DAYS;
|
|
145
|
+
break;
|
|
146
|
+
case 14:
|
|
147
|
+
lifecyclePolicy = efs.LifecyclePolicy.AFTER_14_DAYS;
|
|
148
|
+
break;
|
|
149
|
+
case 30:
|
|
150
|
+
lifecyclePolicy = efs.LifecyclePolicy.AFTER_30_DAYS;
|
|
151
|
+
break;
|
|
152
|
+
case 60:
|
|
153
|
+
lifecyclePolicy = efs.LifecyclePolicy.AFTER_60_DAYS;
|
|
154
|
+
break;
|
|
155
|
+
case 90:
|
|
156
|
+
lifecyclePolicy = efs.LifecyclePolicy.AFTER_90_DAYS;
|
|
157
|
+
break;
|
|
158
|
+
default: lifecyclePolicy = efs.LifecyclePolicy.AFTER_30_DAYS;
|
|
159
|
+
}
|
|
160
|
+
const fs = new efs.FileSystem(this, 'FileSystem', {
|
|
161
|
+
vpc,
|
|
162
|
+
vpcSubnets: { subnets },
|
|
163
|
+
fileSystemName: config.fileSystemName,
|
|
164
|
+
securityGroup: this.securityGroup,
|
|
165
|
+
performanceMode,
|
|
166
|
+
throughputMode,
|
|
167
|
+
provisionedThroughputPerSecond: config.provisionedThroughput
|
|
168
|
+
? cdk.Size.mebibytes(config.provisionedThroughput)
|
|
169
|
+
: undefined,
|
|
170
|
+
encrypted: config.encrypted ?? true,
|
|
171
|
+
lifecyclePolicy,
|
|
172
|
+
});
|
|
173
|
+
return fs;
|
|
174
|
+
}
|
|
175
|
+
// ==========================================
|
|
176
|
+
// Create Access Points
|
|
177
|
+
// ==========================================
|
|
178
|
+
createAccessPoints(config) {
|
|
179
|
+
if (!config.accessPoints || config.accessPoints.length === 0) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
for (const apConfig of config.accessPoints) {
|
|
183
|
+
const uid = apConfig.posixUid ?? 33; // www-data
|
|
184
|
+
const gid = apConfig.posixGid ?? 33;
|
|
185
|
+
const permissions = apConfig.permissions ?? '755';
|
|
186
|
+
const accessPoint = new efs.AccessPoint(this, `AccessPoint${apConfig.name}`, {
|
|
187
|
+
fileSystem: this.fileSystem,
|
|
188
|
+
path: apConfig.path,
|
|
189
|
+
posixUser: {
|
|
190
|
+
uid: uid.toString(),
|
|
191
|
+
gid: gid.toString(),
|
|
192
|
+
},
|
|
193
|
+
createAcl: {
|
|
194
|
+
ownerUid: uid.toString(),
|
|
195
|
+
ownerGid: gid.toString(),
|
|
196
|
+
permissions,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
accessPoint.applyRemovalPolicy(this.removalPolicy);
|
|
200
|
+
this.accessPoints.set(apConfig.name, accessPoint);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// ==========================================
|
|
204
|
+
// Outputs
|
|
205
|
+
// ==========================================
|
|
206
|
+
createOutputs(config) {
|
|
207
|
+
new cdk.CfnOutput(this, 'FileSystemId', {
|
|
208
|
+
value: this.fileSystem.fileSystemId,
|
|
209
|
+
description: 'EFS File System ID',
|
|
210
|
+
exportName: `${config.stackName}-FileSystemId`,
|
|
211
|
+
});
|
|
212
|
+
new cdk.CfnOutput(this, 'FileSystemArn', {
|
|
213
|
+
value: this.fileSystem.fileSystemArn,
|
|
214
|
+
description: 'EFS File System ARN',
|
|
215
|
+
exportName: `${config.stackName}-FileSystemArn`,
|
|
216
|
+
});
|
|
217
|
+
new cdk.CfnOutput(this, 'SecurityGroupId', {
|
|
218
|
+
value: this.securityGroup.securityGroupId,
|
|
219
|
+
description: 'EFS Security Group ID',
|
|
220
|
+
exportName: `${config.stackName}-EfsSecurityGroupId`,
|
|
221
|
+
});
|
|
222
|
+
// Output access point IDs
|
|
223
|
+
for (const [name, ap] of this.accessPoints) {
|
|
224
|
+
new cdk.CfnOutput(this, `AccessPointId${name}`, {
|
|
225
|
+
value: ap.accessPointId,
|
|
226
|
+
description: `Access Point ID for ${name}`,
|
|
227
|
+
exportName: `${config.stackName}-AccessPointId-${name}`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// ==========================================
|
|
232
|
+
// Public Methods
|
|
233
|
+
// ==========================================
|
|
234
|
+
/**
|
|
235
|
+
* Allow NFS access from a security group
|
|
236
|
+
*/
|
|
237
|
+
allowNfsFrom(sg, description) {
|
|
238
|
+
this.securityGroup.addIngressRule(ec2.Peer.securityGroupId(sg.securityGroupId), ec2.Port.tcp(2049), description ?? 'Allow NFS from specified security group');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.EfsConstruct = EfsConstruct;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
3
|
+
import * as elasticache from 'aws-cdk-lib/aws-elasticache';
|
|
4
|
+
import { ElastiCacheConfig } from '../interfaces/elasticache-config';
|
|
5
|
+
import { VpcConstruct } from './vpc';
|
|
6
|
+
export interface ElastiCacheConstructProps {
|
|
7
|
+
config: ElastiCacheConfig;
|
|
8
|
+
/** VpcConstruct from NetworkStack */
|
|
9
|
+
vpcConstruct: VpcConstruct;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* ElastiCache Redis Construct - สร้าง Redis Replication Group
|
|
13
|
+
*
|
|
14
|
+
* Features:
|
|
15
|
+
* - Multi-AZ with automatic failover
|
|
16
|
+
* - Encryption at rest and in transit
|
|
17
|
+
* - Subnet group for isolated subnets
|
|
18
|
+
*/
|
|
19
|
+
export declare class ElastiCacheConstruct extends Construct {
|
|
20
|
+
readonly replicationGroup: elasticache.CfnReplicationGroup;
|
|
21
|
+
readonly securityGroup: ec2.SecurityGroup;
|
|
22
|
+
readonly subnetGroup: elasticache.CfnSubnetGroup;
|
|
23
|
+
private readonly removalPolicy;
|
|
24
|
+
constructor(scope: Construct, id: string, props: ElastiCacheConstructProps);
|
|
25
|
+
private resolveVpc;
|
|
26
|
+
private createSecurityGroup;
|
|
27
|
+
private createSubnetGroup;
|
|
28
|
+
private createParameterGroup;
|
|
29
|
+
private createReplicationGroup;
|
|
30
|
+
private createOutputs;
|
|
31
|
+
/**
|
|
32
|
+
* Allow Redis access from a security group
|
|
33
|
+
*/
|
|
34
|
+
allowRedisFrom(sg: ec2.ISecurityGroup, description?: string): void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ElastiCacheConstruct = void 0;
|
|
37
|
+
const constructs_1 = require("constructs");
|
|
38
|
+
const cdk = __importStar(require("aws-cdk-lib"));
|
|
39
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
40
|
+
const ec2 = __importStar(require("aws-cdk-lib/aws-ec2"));
|
|
41
|
+
const elasticache = __importStar(require("aws-cdk-lib/aws-elasticache"));
|
|
42
|
+
/**
|
|
43
|
+
* ElastiCache Redis Construct - สร้าง Redis Replication Group
|
|
44
|
+
*
|
|
45
|
+
* Features:
|
|
46
|
+
* - Multi-AZ with automatic failover
|
|
47
|
+
* - Encryption at rest and in transit
|
|
48
|
+
* - Subnet group for isolated subnets
|
|
49
|
+
*/
|
|
50
|
+
class ElastiCacheConstruct extends constructs_1.Construct {
|
|
51
|
+
constructor(scope, id, props) {
|
|
52
|
+
super(scope, id);
|
|
53
|
+
const { config, vpcConstruct } = props;
|
|
54
|
+
this.removalPolicy = config.removalPolicy === 'destroy'
|
|
55
|
+
? aws_cdk_lib_1.RemovalPolicy.DESTROY
|
|
56
|
+
: aws_cdk_lib_1.RemovalPolicy.RETAIN;
|
|
57
|
+
// A. Resolve VPC & Subnets
|
|
58
|
+
const { vpc, subnetIds } = this.resolveVpc(config, vpcConstruct);
|
|
59
|
+
// B. Create Security Group
|
|
60
|
+
this.securityGroup = this.createSecurityGroup(vpc, config);
|
|
61
|
+
// C. Create Subnet Group
|
|
62
|
+
this.subnetGroup = this.createSubnetGroup(config, subnetIds);
|
|
63
|
+
// D. Create Parameter Group (optional)
|
|
64
|
+
const parameterGroup = this.createParameterGroup(config);
|
|
65
|
+
// E. Create Replication Group
|
|
66
|
+
this.replicationGroup = this.createReplicationGroup(config, parameterGroup);
|
|
67
|
+
// F. Apply Removal Policy
|
|
68
|
+
this.replicationGroup.applyRemovalPolicy(this.removalPolicy);
|
|
69
|
+
this.securityGroup.applyRemovalPolicy(this.removalPolicy);
|
|
70
|
+
// G. Outputs
|
|
71
|
+
this.createOutputs(config);
|
|
72
|
+
}
|
|
73
|
+
// ==========================================
|
|
74
|
+
// Resolve VPC
|
|
75
|
+
// ==========================================
|
|
76
|
+
resolveVpc(config, vpcConstruct) {
|
|
77
|
+
const subnetName = config.source.subnetName ?? 'cache';
|
|
78
|
+
const cfnSubnets = vpcConstruct.subnetsByName.get(subnetName);
|
|
79
|
+
if (!cfnSubnets || cfnSubnets.length === 0) {
|
|
80
|
+
throw new Error(`No subnets found with name "${subnetName}" in VPC construct. ` +
|
|
81
|
+
`Available: ${Array.from(vpcConstruct.subnetsByName.keys()).join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
// Build subnetIds and routeTableIds from VpcConstruct
|
|
84
|
+
const subnetIds = [];
|
|
85
|
+
const azs = [];
|
|
86
|
+
const routeTableIds = [];
|
|
87
|
+
vpcConstruct.subnets.forEach((cfnSubnet, subnetKey) => {
|
|
88
|
+
if (subnetKey.startsWith(`${subnetName}-`)) {
|
|
89
|
+
const routeTableId = vpcConstruct.subnetRouteTableMap.get(subnetKey);
|
|
90
|
+
subnetIds.push(cfnSubnet.ref);
|
|
91
|
+
azs.push(cfnSubnet.availabilityZone);
|
|
92
|
+
if (routeTableId)
|
|
93
|
+
routeTableIds.push(routeTableId);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const hasRtIds = routeTableIds.length === subnetIds.length;
|
|
97
|
+
const vpc = ec2.Vpc.fromVpcAttributes(this, 'Vpc', {
|
|
98
|
+
vpcId: vpcConstruct.vpc.ref,
|
|
99
|
+
availabilityZones: azs,
|
|
100
|
+
isolatedSubnetIds: subnetIds,
|
|
101
|
+
isolatedSubnetRouteTableIds: hasRtIds ? routeTableIds : undefined,
|
|
102
|
+
});
|
|
103
|
+
return { vpc, subnetIds };
|
|
104
|
+
}
|
|
105
|
+
// ==========================================
|
|
106
|
+
// Security Group
|
|
107
|
+
// ==========================================
|
|
108
|
+
createSecurityGroup(vpc, config) {
|
|
109
|
+
const port = config.port ?? 6379;
|
|
110
|
+
const sg = new ec2.SecurityGroup(this, 'RedisSecurityGroup', {
|
|
111
|
+
vpc,
|
|
112
|
+
securityGroupName: config.securityGroupName,
|
|
113
|
+
description: `Security group for ${config.clusterName} ElastiCache Redis`,
|
|
114
|
+
allowAllOutbound: true, // Allow response traffic back to clients (ECS, Bastion)
|
|
115
|
+
});
|
|
116
|
+
// Ingress rules will be added by ECS service construct
|
|
117
|
+
return sg;
|
|
118
|
+
}
|
|
119
|
+
// ==========================================
|
|
120
|
+
// Subnet Group
|
|
121
|
+
// ==========================================
|
|
122
|
+
createSubnetGroup(config, subnetIds) {
|
|
123
|
+
return new elasticache.CfnSubnetGroup(this, 'SubnetGroup', {
|
|
124
|
+
description: `Subnet group for ${config.clusterName} Redis`,
|
|
125
|
+
subnetIds,
|
|
126
|
+
cacheSubnetGroupName: `${config.clusterName}-subnet-group`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// ==========================================
|
|
130
|
+
// Parameter Group
|
|
131
|
+
// ==========================================
|
|
132
|
+
createParameterGroup(config) {
|
|
133
|
+
if (!config.parameters || Object.keys(config.parameters).length === 0) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
const family = config.parameterGroupFamily ?? 'redis7';
|
|
137
|
+
return new elasticache.CfnParameterGroup(this, 'ParameterGroup', {
|
|
138
|
+
cacheParameterGroupFamily: family,
|
|
139
|
+
description: `Parameter group for ${config.clusterName} Redis`,
|
|
140
|
+
properties: config.parameters,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// ==========================================
|
|
144
|
+
// Replication Group
|
|
145
|
+
// ==========================================
|
|
146
|
+
createReplicationGroup(config, parameterGroup) {
|
|
147
|
+
const port = config.port ?? 6379;
|
|
148
|
+
const numCacheClusters = config.numCacheClusters ?? 2;
|
|
149
|
+
const multiAz = config.multiAzEnabled ?? true;
|
|
150
|
+
const automaticFailover = config.automaticFailoverEnabled ?? true;
|
|
151
|
+
const replicationGroup = new elasticache.CfnReplicationGroup(this, 'ReplicationGroup', {
|
|
152
|
+
replicationGroupDescription: `Redis cluster for ${config.clusterName}`,
|
|
153
|
+
replicationGroupId: config.clusterName,
|
|
154
|
+
engine: 'redis',
|
|
155
|
+
engineVersion: config.engineVersion ?? '7.0',
|
|
156
|
+
cacheNodeType: config.nodeType ?? 'cache.t3.micro',
|
|
157
|
+
numCacheClusters,
|
|
158
|
+
automaticFailoverEnabled: numCacheClusters > 1 ? automaticFailover : false,
|
|
159
|
+
multiAzEnabled: numCacheClusters > 1 ? multiAz : false,
|
|
160
|
+
cacheSubnetGroupName: this.subnetGroup.cacheSubnetGroupName,
|
|
161
|
+
securityGroupIds: [this.securityGroup.securityGroupId],
|
|
162
|
+
port,
|
|
163
|
+
atRestEncryptionEnabled: config.atRestEncryptionEnabled ?? true,
|
|
164
|
+
transitEncryptionEnabled: config.transitEncryptionEnabled ?? true,
|
|
165
|
+
cacheParameterGroupName: parameterGroup?.ref,
|
|
166
|
+
snapshotRetentionLimit: config.snapshotRetentionLimit ?? 5,
|
|
167
|
+
snapshotWindow: config.snapshotWindow ?? '03:00-05:00',
|
|
168
|
+
preferredMaintenanceWindow: config.maintenanceWindow ?? 'sun:05:00-sun:07:00',
|
|
169
|
+
});
|
|
170
|
+
// Add dependency on subnet group
|
|
171
|
+
replicationGroup.addDependency(this.subnetGroup);
|
|
172
|
+
return replicationGroup;
|
|
173
|
+
}
|
|
174
|
+
// ==========================================
|
|
175
|
+
// Outputs
|
|
176
|
+
// ==========================================
|
|
177
|
+
createOutputs(config) {
|
|
178
|
+
new cdk.CfnOutput(this, 'ReplicationGroupId', {
|
|
179
|
+
value: this.replicationGroup.ref,
|
|
180
|
+
description: 'ElastiCache Replication Group ID',
|
|
181
|
+
exportName: `${config.stackName}-ReplicationGroupId`,
|
|
182
|
+
});
|
|
183
|
+
new cdk.CfnOutput(this, 'PrimaryEndpoint', {
|
|
184
|
+
value: this.replicationGroup.attrPrimaryEndPointAddress,
|
|
185
|
+
description: 'Redis Primary Endpoint Address',
|
|
186
|
+
exportName: `${config.stackName}-PrimaryEndpoint`,
|
|
187
|
+
});
|
|
188
|
+
new cdk.CfnOutput(this, 'PrimaryEndpointPort', {
|
|
189
|
+
value: this.replicationGroup.attrPrimaryEndPointPort,
|
|
190
|
+
description: 'Redis Primary Endpoint Port',
|
|
191
|
+
exportName: `${config.stackName}-PrimaryEndpointPort`,
|
|
192
|
+
});
|
|
193
|
+
new cdk.CfnOutput(this, 'SecurityGroupId', {
|
|
194
|
+
value: this.securityGroup.securityGroupId,
|
|
195
|
+
description: 'Redis Security Group ID',
|
|
196
|
+
exportName: `${config.stackName}-RedisSecurityGroupId`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// ==========================================
|
|
200
|
+
// Public Methods
|
|
201
|
+
// ==========================================
|
|
202
|
+
/**
|
|
203
|
+
* Allow Redis access from a security group
|
|
204
|
+
*/
|
|
205
|
+
allowRedisFrom(sg, description) {
|
|
206
|
+
const port = 6379;
|
|
207
|
+
this.securityGroup.addIngressRule(ec2.Peer.securityGroupId(sg.securityGroupId), ec2.Port.tcp(port), description ?? 'Allow Redis from specified security group');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
exports.ElastiCacheConstruct = ElastiCacheConstruct;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
3
|
+
export interface NaclRuleConfig {
|
|
4
|
+
ruleNumber: number;
|
|
5
|
+
protocol: number;
|
|
6
|
+
ruleAction: 'allow' | 'deny';
|
|
7
|
+
cidr: string;
|
|
8
|
+
fromPort?: number;
|
|
9
|
+
toPort?: number;
|
|
10
|
+
egress: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface NaclConfig {
|
|
13
|
+
name: string;
|
|
14
|
+
subnetNames: string[];
|
|
15
|
+
rules: NaclRuleConfig[];
|
|
16
|
+
}
|
|
17
|
+
export interface NaclConstructProps {
|
|
18
|
+
/** VPC ID (can be ref or imported value) */
|
|
19
|
+
vpcId: string;
|
|
20
|
+
/** Prefix for resource naming */
|
|
21
|
+
namePrefix: string;
|
|
22
|
+
/** NACL configurations */
|
|
23
|
+
nacls: NaclConfig[];
|
|
24
|
+
/** CfnSubnet objects grouped by name */
|
|
25
|
+
subnets: Map<string, ec2.CfnSubnet[]>;
|
|
26
|
+
/** Removal policy (default: 'destroy') */
|
|
27
|
+
removalPolicy?: 'destroy' | 'retain';
|
|
28
|
+
/** Additional tags */
|
|
29
|
+
tags?: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* NACL construct - สร้าง NACLs และ associate กับ subnets
|
|
33
|
+
*/
|
|
34
|
+
export declare class NaclConstruct extends Construct {
|
|
35
|
+
readonly nacls: Map<string, ec2.CfnNetworkAcl>;
|
|
36
|
+
constructor(scope: Construct, id: string, props: NaclConstructProps);
|
|
37
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.NaclConstruct = void 0;
|
|
37
|
+
const constructs_1 = require("constructs");
|
|
38
|
+
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
39
|
+
const ec2 = __importStar(require("aws-cdk-lib/aws-ec2"));
|
|
40
|
+
/**
|
|
41
|
+
* NACL construct - สร้าง NACLs และ associate กับ subnets
|
|
42
|
+
*/
|
|
43
|
+
class NaclConstruct extends constructs_1.Construct {
|
|
44
|
+
constructor(scope, id, props) {
|
|
45
|
+
super(scope, id);
|
|
46
|
+
this.nacls = new Map();
|
|
47
|
+
const { vpcId, namePrefix, nacls, subnets, removalPolicy: rp, tags } = props;
|
|
48
|
+
const removalPolicy = rp === 'retain' ? aws_cdk_lib_1.RemovalPolicy.RETAIN : aws_cdk_lib_1.RemovalPolicy.DESTROY;
|
|
49
|
+
nacls.forEach((naclConfig) => {
|
|
50
|
+
// Create NACL
|
|
51
|
+
const nacl = new ec2.CfnNetworkAcl(this, `Nacl-${naclConfig.name}`, {
|
|
52
|
+
vpcId: vpcId,
|
|
53
|
+
tags: [
|
|
54
|
+
{ key: 'Name', value: `${namePrefix}-${naclConfig.name}-nacl` },
|
|
55
|
+
...Object.entries(tags || {}).map(([key, value]) => ({ key, value })),
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
nacl.applyRemovalPolicy(removalPolicy);
|
|
59
|
+
this.nacls.set(naclConfig.name, nacl);
|
|
60
|
+
// Add rules
|
|
61
|
+
naclConfig.rules.forEach((rule, ruleIndex) => {
|
|
62
|
+
new ec2.CfnNetworkAclEntry(this, `NaclRule-${naclConfig.name}-${ruleIndex}`, {
|
|
63
|
+
networkAclId: nacl.ref,
|
|
64
|
+
ruleNumber: rule.ruleNumber,
|
|
65
|
+
protocol: rule.protocol,
|
|
66
|
+
ruleAction: rule.ruleAction,
|
|
67
|
+
cidrBlock: rule.cidr,
|
|
68
|
+
egress: rule.egress,
|
|
69
|
+
portRange: rule.fromPort !== undefined ? {
|
|
70
|
+
from: rule.fromPort,
|
|
71
|
+
to: rule.toPort ?? rule.fromPort,
|
|
72
|
+
} : undefined,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
// Associate with subnets
|
|
76
|
+
naclConfig.subnetNames.forEach((subnetName) => {
|
|
77
|
+
const subnetList = subnets.get(subnetName) || [];
|
|
78
|
+
subnetList.forEach((subnet, index) => {
|
|
79
|
+
new ec2.CfnSubnetNetworkAclAssociation(this, `NaclAssoc-${naclConfig.name}-${subnetName}-${index}`, {
|
|
80
|
+
subnetId: subnet.ref,
|
|
81
|
+
networkAclId: nacl.ref,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.NaclConstruct = NaclConstruct;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Construct } from 'constructs';
|
|
2
|
+
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
3
|
+
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
|
|
4
|
+
import { NlbConfig } from '../interfaces/nlb-config';
|
|
5
|
+
import { VpcConstruct } from './vpc';
|
|
6
|
+
import { AlbConstruct } from './alb';
|
|
7
|
+
export interface NlbConstructProps {
|
|
8
|
+
config: NlbConfig;
|
|
9
|
+
vpcConstruct: VpcConstruct;
|
|
10
|
+
/** ALB construct ที่จะเป็น target ของ NLB */
|
|
11
|
+
albConstruct: AlbConstruct;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* NLB Construct - สร้าง NLB (internet-facing) + Security Group + Listeners
|
|
15
|
+
*
|
|
16
|
+
* Pattern: NLB (internet-facing) → ALB (internal)
|
|
17
|
+
* - NLB SG: allow inbound from internet
|
|
18
|
+
* - ALB SG: auto-add ingress from NLB SG (via CfnSecurityGroupIngress เพื่อหลีกเลี่ยง circular dependency)
|
|
19
|
+
* - Target Group: type ALB, forward traffic ไปที่ internal ALB
|
|
20
|
+
*/
|
|
21
|
+
export declare class NlbConstruct extends Construct {
|
|
22
|
+
readonly nlb: elbv2.NetworkLoadBalancer;
|
|
23
|
+
readonly securityGroup: ec2.SecurityGroup;
|
|
24
|
+
private readonly removalPolicy;
|
|
25
|
+
constructor(scope: Construct, id: string, props: NlbConstructProps);
|
|
26
|
+
private resolveVpc;
|
|
27
|
+
private createSecurityGroup;
|
|
28
|
+
private createNlb;
|
|
29
|
+
private createListeners;
|
|
30
|
+
/**
|
|
31
|
+
* เพิ่ม ingress rule ใน ALB SG ให้รับ traffic จาก NLB SG
|
|
32
|
+
* ใช้ CfnSecurityGroupIngress (L1) แทน sg.addIngressRule (L2)
|
|
33
|
+
* เพื่อหลีกเลี่ยง circular cross-stack dependency
|
|
34
|
+
*
|
|
35
|
+
* Resource นี้จะอยู่ใน NlbStack → reference AlbStack's SG ID ผ่าน Fn::ImportValue (one-way)
|
|
36
|
+
*/
|
|
37
|
+
private addAlbSgIngress;
|
|
38
|
+
private createOutputs;
|
|
39
|
+
}
|