sst 2.22.10 → 2.23.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/bootstrap.js +21 -16
- package/cli/commands/bind.d.ts +0 -2
- package/cli/commands/bind.js +175 -103
- package/cli/ui/deploy.js +32 -3
- package/constructs/AstroSite.d.ts +1 -0
- package/constructs/AstroSite.js +3 -0
- package/constructs/BaseSite.d.ts +0 -71
- package/constructs/Distribution.d.ts +143 -0
- package/constructs/Distribution.js +260 -0
- package/constructs/Function.js +1 -1
- package/constructs/Metadata.d.ts +3 -1
- package/constructs/NextjsSite.d.ts +3 -1
- package/constructs/NextjsSite.js +53 -48
- package/constructs/Service.d.ts +398 -0
- package/constructs/Service.js +584 -0
- package/constructs/SsrSite.d.ts +10 -18
- package/constructs/SsrSite.js +53 -239
- package/constructs/StaticSite.d.ts +20 -16
- package/constructs/StaticSite.js +40 -224
- package/constructs/deprecated/NextjsSite.d.ts +11 -2
- package/constructs/deprecated/NextjsSite.js +13 -0
- package/constructs/index.d.ts +1 -0
- package/constructs/index.js +1 -0
- package/node/event-bus/index.d.ts +4 -1
- package/node/event-bus/index.js +1 -0
- package/package.json +2 -1
- package/project.d.ts +1 -0
- package/runtime/handlers/node.js +3 -0
- package/sst.mjs +243 -107
- package/stacks/deploy.js +4 -0
- package/stacks/metadata.d.ts +1 -0
- package/stacks/metadata.js +17 -0
- package/stacks/monitor.d.ts +1 -1
- package/stacks/monitor.js +1 -0
- package/support/nixpacks/Dockerfile +6 -0
- package/support/service-dev-function/index.js +1 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import url from "url";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { VisibleError } from "../error.js";
|
|
5
|
+
import { execAsync } from "../util/process.js";
|
|
6
|
+
import { existsAsync } from "../util/fs.js";
|
|
7
|
+
import { Colors } from "../cli/colors.js";
|
|
8
|
+
import { Construct } from "constructs";
|
|
9
|
+
import { Duration as CdkDuration } from "aws-cdk-lib/core";
|
|
10
|
+
import { Role, Effect, PolicyStatement, AccountPrincipal, ServicePrincipal, CompositePrincipal, } from "aws-cdk-lib/aws-iam";
|
|
11
|
+
import { ViewerProtocolPolicy, AllowedMethods, CachedMethods, CachePolicy, CacheQueryStringBehavior, CacheHeaderBehavior, CacheCookieBehavior, OriginProtocolPolicy, OriginRequestPolicy, } from "aws-cdk-lib/aws-cloudfront";
|
|
12
|
+
import { HttpOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
13
|
+
import { Stack } from "./Stack.js";
|
|
14
|
+
import { Distribution } from "./Distribution.js";
|
|
15
|
+
import { Function } from "./Function.js";
|
|
16
|
+
import { Secret } from "./Secret.js";
|
|
17
|
+
import { useDeferredTasks } from "./deferred_task.js";
|
|
18
|
+
import { attachPermissionsToRole } from "./util/permission.js";
|
|
19
|
+
import { bindEnvironment, bindPermissions, getParameterPath, getReferencedSecrets, } from "./util/functionBinding.js";
|
|
20
|
+
import { useProject } from "../project.js";
|
|
21
|
+
import { Vpc, } from "aws-cdk-lib/aws-ec2";
|
|
22
|
+
import { AwsLogDriver, Cluster, FargateTaskDefinition, ContainerImage, FargateService, } from "aws-cdk-lib/aws-ecs";
|
|
23
|
+
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
24
|
+
import { Platform } from "aws-cdk-lib/aws-ecr-assets";
|
|
25
|
+
import { ApplicationLoadBalancer, } from "aws-cdk-lib/aws-elasticloadbalancingv2";
|
|
26
|
+
import { createAppContext } from "./context.js";
|
|
27
|
+
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
28
|
+
const NIXPACKS_IMAGE_NAME = "sst-nixpacks";
|
|
29
|
+
const supportedCpus = {
|
|
30
|
+
"0.25 vCPU": 256,
|
|
31
|
+
"0.5 vCPU": 512,
|
|
32
|
+
"1 vCPU": 1024,
|
|
33
|
+
"2 vCPU": 2048,
|
|
34
|
+
"4 vCPU": 4096,
|
|
35
|
+
"8 vCPU": 8192,
|
|
36
|
+
"16 vCPU": 16384,
|
|
37
|
+
};
|
|
38
|
+
const supportedMemories = {
|
|
39
|
+
"0.25 vCPU": {
|
|
40
|
+
"0.5 GB": 512,
|
|
41
|
+
"1 GB": 1024,
|
|
42
|
+
"2 GB": 2048,
|
|
43
|
+
},
|
|
44
|
+
"0.5 vCPU": {
|
|
45
|
+
"1 GB": 1024,
|
|
46
|
+
"2 GB": 2048,
|
|
47
|
+
"3 GB": 3072,
|
|
48
|
+
"4 GB": 4096,
|
|
49
|
+
},
|
|
50
|
+
"1 vCPU": {
|
|
51
|
+
"2 GB": 2048,
|
|
52
|
+
"3 GB": 3072,
|
|
53
|
+
"4 GB": 4096,
|
|
54
|
+
"5 GB": 5120,
|
|
55
|
+
"6 GB": 6144,
|
|
56
|
+
"7 GB": 7168,
|
|
57
|
+
"8 GB": 8192,
|
|
58
|
+
},
|
|
59
|
+
"2 vCPU": {
|
|
60
|
+
"4 GB": 4096,
|
|
61
|
+
"5 GB": 5120,
|
|
62
|
+
"6 GB": 6144,
|
|
63
|
+
"7 GB": 7168,
|
|
64
|
+
"8 GB": 8192,
|
|
65
|
+
"9 GB": 9216,
|
|
66
|
+
"10 GB": 10240,
|
|
67
|
+
"11 GB": 11264,
|
|
68
|
+
"12 GB": 12288,
|
|
69
|
+
"13 GB": 13312,
|
|
70
|
+
"14 GB": 14336,
|
|
71
|
+
"15 GB": 15360,
|
|
72
|
+
"16 GB": 16384,
|
|
73
|
+
},
|
|
74
|
+
"4 vCPU": {
|
|
75
|
+
"8 GB": 8192,
|
|
76
|
+
"9 GB": 9216,
|
|
77
|
+
"10 GB": 10240,
|
|
78
|
+
"11 GB": 11264,
|
|
79
|
+
"12 GB": 12288,
|
|
80
|
+
"13 GB": 13312,
|
|
81
|
+
"14 GB": 14336,
|
|
82
|
+
"15 GB": 15360,
|
|
83
|
+
"16 GB": 16384,
|
|
84
|
+
"17 GB": 17408,
|
|
85
|
+
"18 GB": 18432,
|
|
86
|
+
"19 GB": 19456,
|
|
87
|
+
"20 GB": 20480,
|
|
88
|
+
"21 GB": 21504,
|
|
89
|
+
"22 GB": 22528,
|
|
90
|
+
"23 GB": 23552,
|
|
91
|
+
"24 GB": 24576,
|
|
92
|
+
"25 GB": 25600,
|
|
93
|
+
"26 GB": 26624,
|
|
94
|
+
"27 GB": 27648,
|
|
95
|
+
"28 GB": 28672,
|
|
96
|
+
"29 GB": 29696,
|
|
97
|
+
"30 GB": 30720,
|
|
98
|
+
},
|
|
99
|
+
"8 vCPU": {
|
|
100
|
+
"16 GB": 16384,
|
|
101
|
+
"20 GB": 20480,
|
|
102
|
+
"24 GB": 24576,
|
|
103
|
+
"28 GB": 28672,
|
|
104
|
+
"32 GB": 32768,
|
|
105
|
+
"36 GB": 36864,
|
|
106
|
+
"40 GB": 40960,
|
|
107
|
+
"44 GB": 45056,
|
|
108
|
+
"48 GB": 49152,
|
|
109
|
+
"52 GB": 53248,
|
|
110
|
+
"56 GB": 57344,
|
|
111
|
+
"60 GB": 61440,
|
|
112
|
+
},
|
|
113
|
+
"16 vCPU": {
|
|
114
|
+
"32 GB": 32768,
|
|
115
|
+
"40 GB": 40960,
|
|
116
|
+
"48 GB": 49152,
|
|
117
|
+
"56 GB": 57344,
|
|
118
|
+
"64 GB": 65536,
|
|
119
|
+
"72 GB": 73728,
|
|
120
|
+
"80 GB": 81920,
|
|
121
|
+
"88 GB": 90112,
|
|
122
|
+
"96 GB": 98304,
|
|
123
|
+
"104 GB": 106496,
|
|
124
|
+
"112 GB": 114688,
|
|
125
|
+
"120 GB": 122880,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* The `Service` construct is a higher level CDK construct that makes it easy to create modern web apps with Server Side Rendering capabilities.
|
|
130
|
+
* @example
|
|
131
|
+
* Deploys a service in the `app` directory.
|
|
132
|
+
*
|
|
133
|
+
* ```js
|
|
134
|
+
* new Service(stack, "myApp", {
|
|
135
|
+
* path: "app",
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export class Service extends Construct {
|
|
140
|
+
id;
|
|
141
|
+
props;
|
|
142
|
+
doNotDeploy;
|
|
143
|
+
devFunction;
|
|
144
|
+
vpc;
|
|
145
|
+
cluster;
|
|
146
|
+
container;
|
|
147
|
+
taskDefinition;
|
|
148
|
+
distribution;
|
|
149
|
+
constructor(scope, id, props) {
|
|
150
|
+
super(scope, id);
|
|
151
|
+
const app = scope.node.root;
|
|
152
|
+
const stack = Stack.of(this);
|
|
153
|
+
this.id = id;
|
|
154
|
+
this.props = {
|
|
155
|
+
path: ".",
|
|
156
|
+
waitForInvalidation: false,
|
|
157
|
+
cpu: props?.cpu || "0.25 vCPU",
|
|
158
|
+
memory: props?.memory || "0.5 GB",
|
|
159
|
+
port: props?.port || 3000,
|
|
160
|
+
...props,
|
|
161
|
+
};
|
|
162
|
+
this.doNotDeploy =
|
|
163
|
+
!stack.isActive || (app.mode === "dev" && !this.props.dev?.deploy);
|
|
164
|
+
this.validateServiceExists();
|
|
165
|
+
this.validateMemoryAndCpu();
|
|
166
|
+
useServices().add(stack.stackName, id, this.props);
|
|
167
|
+
if (this.doNotDeploy) {
|
|
168
|
+
// @ts-expect-error
|
|
169
|
+
this.vpc = this.cluster = this.container = this.taskDefinition = null;
|
|
170
|
+
// @ts-expect-error
|
|
171
|
+
this.distribution = null;
|
|
172
|
+
this.devFunction = this.createDevFunction();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Create ECS cluster
|
|
176
|
+
const vpc = this.createVpc();
|
|
177
|
+
const { cluster, container, taskDefinition, service } = this.createService(vpc);
|
|
178
|
+
const { alb, target } = this.createLoadBalancer(vpc, service);
|
|
179
|
+
this.createAutoScaling(service, target);
|
|
180
|
+
// Create Distribution
|
|
181
|
+
this.distribution = this.createDistribution(alb);
|
|
182
|
+
this.vpc = vpc;
|
|
183
|
+
this.cluster = cluster;
|
|
184
|
+
this.container = container;
|
|
185
|
+
this.taskDefinition = taskDefinition;
|
|
186
|
+
this.bindForService(props?.bind || []);
|
|
187
|
+
this.attachPermissionsForService(props?.permissions || []);
|
|
188
|
+
Object.entries(props?.environment || {}).map(([key, value]) => this.addEnvironmentForService(key, value));
|
|
189
|
+
useDeferredTasks().add(async () => {
|
|
190
|
+
if (!app.isRunningSSTTest()) {
|
|
191
|
+
Colors.line(`➜ Building container image for the "${this.node.id}" service`);
|
|
192
|
+
// Build app
|
|
193
|
+
let dockerfile = "Dockerfile";
|
|
194
|
+
if (!(await existsAsync(path.join(this.props.path, dockerfile)))) {
|
|
195
|
+
await this.createNixpacksBuilder();
|
|
196
|
+
dockerfile = await this.runNixpacksBuild();
|
|
197
|
+
}
|
|
198
|
+
await this.runDockerBuild(dockerfile);
|
|
199
|
+
this.updateContainerImage(dockerfile, taskDefinition, container);
|
|
200
|
+
}
|
|
201
|
+
// Invalidate CloudFront
|
|
202
|
+
this.distribution.createInvalidation();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/////////////////////
|
|
206
|
+
// Public Properties
|
|
207
|
+
/////////////////////
|
|
208
|
+
/**
|
|
209
|
+
* The CloudFront URL of the website.
|
|
210
|
+
*/
|
|
211
|
+
get url() {
|
|
212
|
+
if (this.doNotDeploy)
|
|
213
|
+
return this.props.dev?.url;
|
|
214
|
+
return this.distribution.url;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* If the custom domain is enabled, this is the URL of the website with the
|
|
218
|
+
* custom domain.
|
|
219
|
+
*/
|
|
220
|
+
get customDomainUrl() {
|
|
221
|
+
if (this.doNotDeploy)
|
|
222
|
+
return;
|
|
223
|
+
return this.distribution.customDomainUrl;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The internally created CDK resources.
|
|
227
|
+
*/
|
|
228
|
+
get cdk() {
|
|
229
|
+
if (this.doNotDeploy)
|
|
230
|
+
return;
|
|
231
|
+
return {
|
|
232
|
+
vpc: this.vpc,
|
|
233
|
+
cluster: this.cluster,
|
|
234
|
+
distribution: this.distribution.cdk.distribution,
|
|
235
|
+
hostedZone: this.distribution.cdk.hostedZone,
|
|
236
|
+
certificate: this.distribution.cdk.certificate,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/////////////////////
|
|
240
|
+
// Public Methods
|
|
241
|
+
/////////////////////
|
|
242
|
+
getConstructMetadata() {
|
|
243
|
+
return {
|
|
244
|
+
type: "Service",
|
|
245
|
+
data: {
|
|
246
|
+
mode: this.doNotDeploy
|
|
247
|
+
? "placeholder"
|
|
248
|
+
: "deployed",
|
|
249
|
+
path: this.props.path,
|
|
250
|
+
customDomainUrl: this.customDomainUrl,
|
|
251
|
+
url: this.url,
|
|
252
|
+
devFunction: this.devFunction?.functionArn,
|
|
253
|
+
task: this.taskDefinition?.taskDefinitionArn,
|
|
254
|
+
container: this.container?.containerName,
|
|
255
|
+
secrets: (this.props.bind || [])
|
|
256
|
+
.filter((c) => c instanceof Secret)
|
|
257
|
+
.map((c) => c.name),
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/** @internal */
|
|
262
|
+
getFunctionBinding() {
|
|
263
|
+
const app = this.node.root;
|
|
264
|
+
return {
|
|
265
|
+
clientPackage: "service",
|
|
266
|
+
variables: {
|
|
267
|
+
url: this.doNotDeploy
|
|
268
|
+
? {
|
|
269
|
+
type: "plain",
|
|
270
|
+
value: this.props.dev?.url ?? "localhost",
|
|
271
|
+
}
|
|
272
|
+
: {
|
|
273
|
+
// Do not set real value b/c we don't want to make the Lambda function
|
|
274
|
+
// depend on the Site. B/c often the site depends on the Api, causing
|
|
275
|
+
// a CloudFormation circular dependency if the Api and the Site belong
|
|
276
|
+
// to different stacks.
|
|
277
|
+
type: "site_url",
|
|
278
|
+
value: this.customDomainUrl || this.url,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
permissions: {
|
|
282
|
+
"ssm:GetParameters": [
|
|
283
|
+
`arn:${Stack.of(this).partition}:ssm:${app.region}:${app.account}:parameter${getParameterPath(this, "url")}`,
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Binds additional resources to service.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```js
|
|
293
|
+
* service.bind([STRIPE_KEY, bucket]);
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
bind(constructs) {
|
|
297
|
+
this.devFunction?.bind(constructs);
|
|
298
|
+
this.bindForService(constructs);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Attaches the given list of permissions to allow the service
|
|
302
|
+
* to access other AWS resources.
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```js
|
|
306
|
+
* service.attachPermissions(["sns"]);
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
attachPermissions(permissions) {
|
|
310
|
+
this.devFunction?.attachPermissions(permissions);
|
|
311
|
+
this.attachPermissionsForService(permissions);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Attaches additional environment variable to the service.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```js
|
|
318
|
+
* service.addEnvironment({
|
|
319
|
+
* DEBUG: "*"
|
|
320
|
+
* });
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
addEnvironment(name, value) {
|
|
324
|
+
this.devFunction?.addEnvironment(name, value);
|
|
325
|
+
this.addEnvironmentForService(name, value);
|
|
326
|
+
}
|
|
327
|
+
/////////////////////
|
|
328
|
+
// Bundle Cluster
|
|
329
|
+
/////////////////////
|
|
330
|
+
validateServiceExists() {
|
|
331
|
+
const { path: servicePath } = this.props;
|
|
332
|
+
if (!fs.existsSync(servicePath)) {
|
|
333
|
+
throw new Error(`No service found at "${path.resolve(servicePath)}"`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
validateMemoryAndCpu() {
|
|
337
|
+
const { memory, cpu } = this.props;
|
|
338
|
+
if (!supportedCpus[cpu]) {
|
|
339
|
+
throw new Error(`Only the following "cpu" settings are supported for the ${this.node.id} service: ${Object.keys(supportedCpus).join(", ")}`);
|
|
340
|
+
}
|
|
341
|
+
// @ts-ignore
|
|
342
|
+
if (!supportedMemories[cpu][memory]) {
|
|
343
|
+
throw new Error(`Only the following "memory" settings are supported with "${cpu}" for the ${this.node.id} service: ${Object.keys(supportedMemories[cpu]).join(", ")}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
createVpc() {
|
|
347
|
+
const { cdk } = this.props;
|
|
348
|
+
return (cdk?.vpc ??
|
|
349
|
+
new Vpc(this, "Vpc", {
|
|
350
|
+
natGateways: 0,
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
353
|
+
createService(vpc) {
|
|
354
|
+
const { cpu, memory, port } = this.props;
|
|
355
|
+
const app = this.node.root;
|
|
356
|
+
const clusterName = app.logicalPrefixedName(this.node.id);
|
|
357
|
+
const logGroup = new LogGroup(this, "LogGroup", {
|
|
358
|
+
logGroupName: `/sst/service/${clusterName}`,
|
|
359
|
+
retention: RetentionDays.INFINITE,
|
|
360
|
+
});
|
|
361
|
+
const cluster = new Cluster(this, "Cluster", {
|
|
362
|
+
clusterName,
|
|
363
|
+
vpc,
|
|
364
|
+
});
|
|
365
|
+
const taskDefinition = new FargateTaskDefinition(this, `TaskDefinition`, {
|
|
366
|
+
// @ts-ignore
|
|
367
|
+
memoryLimitMiB: supportedMemories[cpu][memory],
|
|
368
|
+
cpu: supportedCpus[cpu],
|
|
369
|
+
});
|
|
370
|
+
const container = taskDefinition.addContainer("Container", {
|
|
371
|
+
image: { bind: () => ({ imageName: "placeholder" }) },
|
|
372
|
+
logging: new AwsLogDriver({
|
|
373
|
+
logGroup,
|
|
374
|
+
streamPrefix: "service",
|
|
375
|
+
}),
|
|
376
|
+
portMappings: [{ containerPort: port }],
|
|
377
|
+
environment: {
|
|
378
|
+
SST_APP: app.name,
|
|
379
|
+
SST_STAGE: app.stage,
|
|
380
|
+
SST_SSM_PREFIX: useProject().config.ssmPrefix,
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
const service = new FargateService(this, "Service", {
|
|
384
|
+
cluster,
|
|
385
|
+
taskDefinition,
|
|
386
|
+
});
|
|
387
|
+
return { cluster, taskDefinition, container, service };
|
|
388
|
+
}
|
|
389
|
+
createLoadBalancer(vpc, service) {
|
|
390
|
+
const alb = new ApplicationLoadBalancer(this, "LoadBalancer", {
|
|
391
|
+
vpc,
|
|
392
|
+
internetFacing: true,
|
|
393
|
+
});
|
|
394
|
+
const listener = alb.addListener("Listener", { port: 80 });
|
|
395
|
+
const target = listener.addTargets("TargetGroup", {
|
|
396
|
+
port: 80,
|
|
397
|
+
targets: [service],
|
|
398
|
+
});
|
|
399
|
+
return { alb, target };
|
|
400
|
+
}
|
|
401
|
+
createAutoScaling(service, target) {
|
|
402
|
+
const { minContainers, maxContainers, cpuUtilization, memoryUtilization, requestsPerContainer, } = this.props.scaling ?? {};
|
|
403
|
+
const scaling = service.autoScaleTaskCount({
|
|
404
|
+
minCapacity: minContainers ?? 1,
|
|
405
|
+
maxCapacity: maxContainers ?? 1,
|
|
406
|
+
});
|
|
407
|
+
scaling.scaleOnCpuUtilization("CpuScaling", {
|
|
408
|
+
targetUtilizationPercent: cpuUtilization ?? 70,
|
|
409
|
+
scaleOutCooldown: CdkDuration.seconds(300),
|
|
410
|
+
});
|
|
411
|
+
scaling.scaleOnMemoryUtilization("MemoryScaling", {
|
|
412
|
+
targetUtilizationPercent: memoryUtilization ?? 70,
|
|
413
|
+
scaleOutCooldown: CdkDuration.seconds(300),
|
|
414
|
+
});
|
|
415
|
+
scaling.scaleOnRequestCount("RequestScaling", {
|
|
416
|
+
requestsPerTarget: requestsPerContainer ?? 500,
|
|
417
|
+
targetGroup: target,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
createDistribution(alb) {
|
|
421
|
+
const { customDomain } = this.props;
|
|
422
|
+
const cachePolicy = new CachePolicy(this, "CachePolicy", {
|
|
423
|
+
queryStringBehavior: CacheQueryStringBehavior.all(),
|
|
424
|
+
headerBehavior: CacheHeaderBehavior.none(),
|
|
425
|
+
cookieBehavior: CacheCookieBehavior.none(),
|
|
426
|
+
defaultTtl: CdkDuration.days(0),
|
|
427
|
+
maxTtl: CdkDuration.days(365),
|
|
428
|
+
minTtl: CdkDuration.days(0),
|
|
429
|
+
enableAcceptEncodingBrotli: true,
|
|
430
|
+
enableAcceptEncodingGzip: true,
|
|
431
|
+
comment: "SST server response cache policy",
|
|
432
|
+
});
|
|
433
|
+
return new Distribution(this, "CDN", {
|
|
434
|
+
customDomain,
|
|
435
|
+
cdk: {
|
|
436
|
+
distribution: {
|
|
437
|
+
defaultRootObject: "",
|
|
438
|
+
defaultBehavior: {
|
|
439
|
+
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
440
|
+
origin: new HttpOrigin(alb.loadBalancerDnsName, {
|
|
441
|
+
protocolPolicy: OriginProtocolPolicy.HTTP_ONLY,
|
|
442
|
+
readTimeout: CdkDuration.seconds(60),
|
|
443
|
+
}),
|
|
444
|
+
allowedMethods: AllowedMethods.ALLOW_ALL,
|
|
445
|
+
cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
|
|
446
|
+
compress: true,
|
|
447
|
+
cachePolicy,
|
|
448
|
+
originRequestPolicy: OriginRequestPolicy.ALL_VIEWER,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
createDevFunction() {
|
|
455
|
+
const { permissions, environment, bind } = this.props;
|
|
456
|
+
const app = this.node.root;
|
|
457
|
+
const role = new Role(this, "ServerFunctionRole", {
|
|
458
|
+
assumedBy: new CompositePrincipal(new AccountPrincipal(app.account), new ServicePrincipal("lambda.amazonaws.com")),
|
|
459
|
+
maxSessionDuration: CdkDuration.hours(12),
|
|
460
|
+
});
|
|
461
|
+
return new Function(this, `ServerFunction`, {
|
|
462
|
+
description: "Service dev function",
|
|
463
|
+
handler: path.join(__dirname, "../support/service-dev-function", "index.handler"),
|
|
464
|
+
runtime: "nodejs18.x",
|
|
465
|
+
memorySize: "512 MB",
|
|
466
|
+
timeout: "10 seconds",
|
|
467
|
+
role,
|
|
468
|
+
bind,
|
|
469
|
+
environment,
|
|
470
|
+
permissions,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
bindForService(constructs) {
|
|
474
|
+
// Get referenced secrets
|
|
475
|
+
const referencedSecrets = [];
|
|
476
|
+
constructs.forEach((c) => referencedSecrets.push(...getReferencedSecrets(c)));
|
|
477
|
+
[...constructs, ...referencedSecrets].forEach((c) => {
|
|
478
|
+
// Bind environment
|
|
479
|
+
const env = bindEnvironment(c);
|
|
480
|
+
Object.entries(env).forEach(([key, value]) => this.addEnvironmentForService(key, value));
|
|
481
|
+
// Bind permissions
|
|
482
|
+
const permissions = bindPermissions(c);
|
|
483
|
+
Object.entries(permissions).forEach(([action, resources]) => this.attachPermissionsForService([
|
|
484
|
+
new PolicyStatement({
|
|
485
|
+
actions: [action],
|
|
486
|
+
effect: Effect.ALLOW,
|
|
487
|
+
resources,
|
|
488
|
+
}),
|
|
489
|
+
]));
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
addEnvironmentForService(name, value) {
|
|
493
|
+
this.container.addEnvironment(name, value);
|
|
494
|
+
}
|
|
495
|
+
attachPermissionsForService(permissions) {
|
|
496
|
+
attachPermissionsToRole(this.taskDefinition.taskRole, permissions);
|
|
497
|
+
}
|
|
498
|
+
/////////////////////
|
|
499
|
+
// Build App
|
|
500
|
+
/////////////////////
|
|
501
|
+
async createNixpacksBuilder() {
|
|
502
|
+
try {
|
|
503
|
+
await execAsync([
|
|
504
|
+
"docker",
|
|
505
|
+
"build",
|
|
506
|
+
`-t ${NIXPACKS_IMAGE_NAME}`,
|
|
507
|
+
"--platform=linux/amd64",
|
|
508
|
+
path.resolve(__dirname, "../support/nixpacks"),
|
|
509
|
+
].join(" "), {
|
|
510
|
+
env: {
|
|
511
|
+
...process.env,
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
catch (e) {
|
|
516
|
+
console.error(e);
|
|
517
|
+
throw new VisibleError(`Failed to setup Nixpacks builder for the ${this.node.id} service`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async runNixpacksBuild() {
|
|
521
|
+
const { path: servicePath } = this.props;
|
|
522
|
+
try {
|
|
523
|
+
await execAsync([
|
|
524
|
+
"docker",
|
|
525
|
+
"run",
|
|
526
|
+
"--rm",
|
|
527
|
+
"--network=host",
|
|
528
|
+
`--name=sst-${this.node.id}-service`,
|
|
529
|
+
`-v=${path.resolve(servicePath)}:/service`,
|
|
530
|
+
`-w="/service"`,
|
|
531
|
+
NIXPACKS_IMAGE_NAME,
|
|
532
|
+
`build . --out .`,
|
|
533
|
+
].join(" "), {
|
|
534
|
+
env: {
|
|
535
|
+
...process.env,
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
catch (e) {
|
|
540
|
+
console.error(e);
|
|
541
|
+
throw new VisibleError(`Failed to run Nixpacks build for the ${this.node.id} service`);
|
|
542
|
+
}
|
|
543
|
+
return ".nixpacks/Dockerfile";
|
|
544
|
+
}
|
|
545
|
+
async runDockerBuild(dockerfile) {
|
|
546
|
+
try {
|
|
547
|
+
await execAsync([
|
|
548
|
+
"docker",
|
|
549
|
+
"build",
|
|
550
|
+
`-t sst-build:service-${this.node.id}`,
|
|
551
|
+
"--platform=linux/amd64",
|
|
552
|
+
`-f ${path.join(this.props.path, dockerfile)}`,
|
|
553
|
+
this.props.path,
|
|
554
|
+
].join(" "), {
|
|
555
|
+
env: {
|
|
556
|
+
...process.env,
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
catch (e) {
|
|
561
|
+
console.error(e);
|
|
562
|
+
throw new VisibleError(`Failed to build the ${this.node.id} service`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
updateContainerImage(dockerfile, taskDefinition, container) {
|
|
566
|
+
const image = ContainerImage.fromAsset(this.props.path, {
|
|
567
|
+
platform: Platform.LINUX_AMD64,
|
|
568
|
+
file: dockerfile,
|
|
569
|
+
});
|
|
570
|
+
const cfnTask = taskDefinition.node.defaultChild;
|
|
571
|
+
cfnTask.addPropertyOverride("ContainerDefinitions.0.Image", image.bind(this, container).imageName);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
export const useServices = createAppContext(() => {
|
|
575
|
+
const sites = [];
|
|
576
|
+
return {
|
|
577
|
+
add(stack, name, props) {
|
|
578
|
+
sites.push({ stack, name, props });
|
|
579
|
+
},
|
|
580
|
+
get all() {
|
|
581
|
+
return sites;
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
});
|
package/constructs/SsrSite.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { Bucket, BucketProps, IBucket } from "aws-cdk-lib/aws-s3";
|
|
3
3
|
import { IFunction as ICdkFunction, FunctionProps } from "aws-cdk-lib/aws-lambda";
|
|
4
|
-
import {
|
|
5
|
-
import { Distribution,
|
|
6
|
-
import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
|
|
4
|
+
import { ICachePolicy, IResponseHeadersPolicy, BehaviorOptions, CachePolicy, Function as CfFunction, FunctionEventType as CfFunctionEventType } from "aws-cdk-lib/aws-cloudfront";
|
|
5
|
+
import { Distribution, DistributionDomainProps } from "./Distribution.js";
|
|
7
6
|
import { SSTConstruct } from "./Construct.js";
|
|
8
7
|
import { NodeJSProps } from "./Function.js";
|
|
9
8
|
import { SsrFunction } from "./SsrFunction.js";
|
|
10
9
|
import { EdgeFunction } from "./EdgeFunction.js";
|
|
11
|
-
import { BaseSiteFileOptions,
|
|
10
|
+
import { BaseSiteFileOptions, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
|
|
12
11
|
import { Size } from "./util/size.js";
|
|
13
12
|
import { Duration } from "./util/duration.js";
|
|
14
13
|
import { Permissions } from "./util/permission.js";
|
|
@@ -25,7 +24,7 @@ export type SsrBuildConfig = {
|
|
|
25
24
|
};
|
|
26
25
|
export interface SsrSiteNodeJSProps extends NodeJSProps {
|
|
27
26
|
}
|
|
28
|
-
export interface SsrDomainProps extends
|
|
27
|
+
export interface SsrDomainProps extends DistributionDomainProps {
|
|
29
28
|
}
|
|
30
29
|
export interface SsrSiteFileOptions extends BaseSiteFileOptions {
|
|
31
30
|
}
|
|
@@ -280,8 +279,6 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
280
279
|
private cfFunction;
|
|
281
280
|
private s3Origin;
|
|
282
281
|
private distribution;
|
|
283
|
-
private hostedZone?;
|
|
284
|
-
private certificate?;
|
|
285
282
|
constructor(scope: Construct, id: string, props?: SsrSiteProps);
|
|
286
283
|
/**
|
|
287
284
|
* The CloudFront URL of the website.
|
|
@@ -298,9 +295,9 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
298
295
|
get cdk(): {
|
|
299
296
|
function: ICdkFunction | undefined;
|
|
300
297
|
bucket: Bucket;
|
|
301
|
-
distribution:
|
|
302
|
-
hostedZone: IHostedZone | undefined;
|
|
303
|
-
certificate: ICertificate | undefined;
|
|
298
|
+
distribution: import("aws-cdk-lib/aws-cloudfront").IDistribution;
|
|
299
|
+
hostedZone: import("aws-cdk-lib/aws-route53").IHostedZone | undefined;
|
|
300
|
+
certificate: import("aws-cdk-lib/aws-certificatemanager").ICertificate | undefined;
|
|
304
301
|
} | undefined;
|
|
305
302
|
/**
|
|
306
303
|
* Attaches the given list of permissions to allow the server side
|
|
@@ -340,12 +337,10 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
340
337
|
protected createFunctionForDev(): SsrFunction;
|
|
341
338
|
private grantServerS3Permissions;
|
|
342
339
|
private grantServerCloudFrontPermissions;
|
|
343
|
-
private validateCloudFrontDistributionSettings;
|
|
344
340
|
private createCloudFrontS3Origin;
|
|
345
341
|
private createCloudFrontFunction;
|
|
346
342
|
protected createCloudFrontDistributionForRegional(): Distribution;
|
|
347
343
|
protected createCloudFrontDistributionForEdge(): Distribution;
|
|
348
|
-
protected buildDistributionDomainNames(): string[];
|
|
349
344
|
protected buildDefaultBehaviorForRegional(cachePolicy: ICachePolicy): BehaviorOptions;
|
|
350
345
|
protected buildDefaultBehaviorForEdge(cachePolicy: ICachePolicy): BehaviorOptions;
|
|
351
346
|
protected buildBehaviorFunctionAssociations(): {
|
|
@@ -355,20 +350,17 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
355
350
|
protected addStaticFileBehaviors(): void;
|
|
356
351
|
protected buildServerCachePolicy(allowedHeaders?: string[]): CachePolicy;
|
|
357
352
|
protected buildServerOriginRequestPolicy(): import("aws-cdk-lib/aws-cloudfront").IOriginRequestPolicy;
|
|
358
|
-
private createCloudFrontInvalidation;
|
|
359
|
-
protected validateCustomDomainSettings(): void;
|
|
360
|
-
protected lookupHostedZone(): IHostedZone | undefined;
|
|
361
|
-
private createCertificate;
|
|
362
|
-
protected createRoute53Records(): void;
|
|
363
353
|
private getS3ContentReplaceValues;
|
|
364
354
|
private validateSiteExists;
|
|
365
355
|
private validateTimeout;
|
|
366
356
|
private writeTypesFile;
|
|
367
357
|
protected generateBuildId(): string;
|
|
358
|
+
protected supportsStreaming(): boolean;
|
|
368
359
|
}
|
|
369
360
|
export declare const useSites: () => {
|
|
370
|
-
add(name: string, type: string, props: SsrSiteNormalizedProps): void;
|
|
361
|
+
add(stack: string, name: string, type: string, props: SsrSiteNormalizedProps): void;
|
|
371
362
|
readonly all: {
|
|
363
|
+
stack: string;
|
|
372
364
|
name: string;
|
|
373
365
|
type: string;
|
|
374
366
|
props: SsrSiteNormalizedProps;
|