sst 2.22.11 → 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 +2 -3
- package/constructs/AstroSite.js +3 -4
- package/constructs/BaseSite.d.ts +0 -71
- package/constructs/Distribution.d.ts +143 -0
- package/constructs/Distribution.js +260 -0
- package/constructs/Metadata.d.ts +3 -1
- package/constructs/NextjsSite.d.ts +3 -2
- package/constructs/NextjsSite.js +51 -42
- package/constructs/Service.d.ts +398 -0
- package/constructs/Service.js +584 -0
- package/constructs/SsrSite.d.ts +10 -25
- package/constructs/SsrSite.js +52 -241
- 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/sst.mjs +240 -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
|
}
|
|
@@ -248,12 +247,6 @@ export interface SsrSiteProps {
|
|
|
248
247
|
* ```
|
|
249
248
|
*/
|
|
250
249
|
fileOptions?: SsrSiteFileOptions[];
|
|
251
|
-
/**
|
|
252
|
-
* The SSR function url supports streaming.
|
|
253
|
-
* [Read more](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html#config-rs-invoke-furls).
|
|
254
|
-
* @default false
|
|
255
|
-
*/
|
|
256
|
-
streaming?: boolean;
|
|
257
250
|
}
|
|
258
251
|
type SsrSiteNormalizedProps = SsrSiteProps & {
|
|
259
252
|
path: Exclude<SsrSiteProps["path"], undefined>;
|
|
@@ -286,9 +279,6 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
286
279
|
private cfFunction;
|
|
287
280
|
private s3Origin;
|
|
288
281
|
private distribution;
|
|
289
|
-
private hostedZone?;
|
|
290
|
-
private certificate?;
|
|
291
|
-
private streaming?;
|
|
292
282
|
constructor(scope: Construct, id: string, props?: SsrSiteProps);
|
|
293
283
|
/**
|
|
294
284
|
* The CloudFront URL of the website.
|
|
@@ -305,9 +295,9 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
305
295
|
get cdk(): {
|
|
306
296
|
function: ICdkFunction | undefined;
|
|
307
297
|
bucket: Bucket;
|
|
308
|
-
distribution:
|
|
309
|
-
hostedZone: IHostedZone | undefined;
|
|
310
|
-
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;
|
|
311
301
|
} | undefined;
|
|
312
302
|
/**
|
|
313
303
|
* Attaches the given list of permissions to allow the server side
|
|
@@ -347,12 +337,10 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
347
337
|
protected createFunctionForDev(): SsrFunction;
|
|
348
338
|
private grantServerS3Permissions;
|
|
349
339
|
private grantServerCloudFrontPermissions;
|
|
350
|
-
private validateCloudFrontDistributionSettings;
|
|
351
340
|
private createCloudFrontS3Origin;
|
|
352
341
|
private createCloudFrontFunction;
|
|
353
342
|
protected createCloudFrontDistributionForRegional(): Distribution;
|
|
354
343
|
protected createCloudFrontDistributionForEdge(): Distribution;
|
|
355
|
-
protected buildDistributionDomainNames(): string[];
|
|
356
344
|
protected buildDefaultBehaviorForRegional(cachePolicy: ICachePolicy): BehaviorOptions;
|
|
357
345
|
protected buildDefaultBehaviorForEdge(cachePolicy: ICachePolicy): BehaviorOptions;
|
|
358
346
|
protected buildBehaviorFunctionAssociations(): {
|
|
@@ -362,20 +350,17 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
362
350
|
protected addStaticFileBehaviors(): void;
|
|
363
351
|
protected buildServerCachePolicy(allowedHeaders?: string[]): CachePolicy;
|
|
364
352
|
protected buildServerOriginRequestPolicy(): import("aws-cdk-lib/aws-cloudfront").IOriginRequestPolicy;
|
|
365
|
-
private createCloudFrontInvalidation;
|
|
366
|
-
protected validateCustomDomainSettings(): void;
|
|
367
|
-
protected lookupHostedZone(): IHostedZone | undefined;
|
|
368
|
-
private createCertificate;
|
|
369
|
-
protected createRoute53Records(): void;
|
|
370
353
|
private getS3ContentReplaceValues;
|
|
371
354
|
private validateSiteExists;
|
|
372
355
|
private validateTimeout;
|
|
373
356
|
private writeTypesFile;
|
|
374
357
|
protected generateBuildId(): string;
|
|
358
|
+
protected supportsStreaming(): boolean;
|
|
375
359
|
}
|
|
376
360
|
export declare const useSites: () => {
|
|
377
|
-
add(name: string, type: string, props: SsrSiteNormalizedProps): void;
|
|
361
|
+
add(stack: string, name: string, type: string, props: SsrSiteNormalizedProps): void;
|
|
378
362
|
readonly all: {
|
|
363
|
+
stack: string;
|
|
379
364
|
name: string;
|
|
380
365
|
type: string;
|
|
381
366
|
props: SsrSiteNormalizedProps;
|