sst 2.18.3 → 2.19.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.
@@ -12,17 +12,20 @@ import * as functionUrlCors from "./util/functionUrlCors.js";
12
12
  import url from "url";
13
13
  import { useDeferredTasks } from "./deferred_task.js";
14
14
  import { useProject } from "../project.js";
15
+ import { VisibleError } from "../error.js";
15
16
  import { useRuntimeHandlers } from "../runtime/handlers.js";
16
17
  import { createAppContext } from "./context.js";
17
18
  import { useWarning } from "./util/warning.js";
18
- import { Architecture, AssetCode, Code, Function as CDKFunction, FunctionUrlAuthType, LayerVersion, Runtime as CDKRuntime, Tracing, } from "aws-cdk-lib/aws-lambda";
19
+ import { Architecture, AssetCode, Code, Function as CDKFunction, FunctionUrlAuthType, Handler as CDKHandler, LayerVersion, Runtime as CDKRuntime, Tracing, } from "aws-cdk-lib/aws-lambda";
19
20
  import { RetentionDays } from "aws-cdk-lib/aws-logs";
20
21
  import { Token, Size as CDKSize, Duration as CDKDuration, } from "aws-cdk-lib/core";
21
22
  import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam";
22
23
  import { StringParameter } from "aws-cdk-lib/aws-ssm";
24
+ import { Platform } from "aws-cdk-lib/aws-ecr-assets";
23
25
  import { useBootstrap } from "../bootstrap.js";
24
26
  const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
25
27
  const supportedRuntimes = {
28
+ container: CDKRuntime.FROM_IMAGE,
26
29
  rust: CDKRuntime.PROVIDED_AL2,
27
30
  nodejs: CDKRuntime.NODEJS,
28
31
  "nodejs4.3": CDKRuntime.NODEJS_4_3,
@@ -38,6 +41,7 @@ const supportedRuntimes = {
38
41
  "python3.7": CDKRuntime.PYTHON_3_7,
39
42
  "python3.8": CDKRuntime.PYTHON_3_8,
40
43
  "python3.9": CDKRuntime.PYTHON_3_9,
44
+ "python3.10": CDKRuntime.PYTHON_3_10,
41
45
  "dotnetcore1.0": CDKRuntime.DOTNET_CORE_1,
42
46
  "dotnetcore2.0": CDKRuntime.DOTNET_CORE_2,
43
47
  "dotnetcore2.1": CDKRuntime.DOTNET_CORE_2_1,
@@ -95,7 +99,7 @@ export class Function extends CDKFunction {
95
99
  return Architecture.ARM_64;
96
100
  if (props.architecture === "x86_64")
97
101
  return Architecture.X86_64;
98
- return undefined;
102
+ return Architecture.X86_64;
99
103
  })();
100
104
  const memorySize = Function.normalizeMemorySize(props.memorySize);
101
105
  const diskSize = Function.normalizeDiskSize(props.diskSize);
@@ -146,17 +150,30 @@ export class Function extends CDKFunction {
146
150
  }
147
151
  super(scope, id, {
148
152
  ...props,
153
+ ...(props.runtime === "container"
154
+ ? {
155
+ code: Code.fromAssetImage(path.resolve(__dirname, "../support/bridge"), {
156
+ ...(architecture?.dockerPlatform
157
+ ? { platform: Platform.custom(architecture.dockerPlatform) }
158
+ : {}),
159
+ }),
160
+ handler: CDKHandler.FROM_IMAGE,
161
+ runtime: CDKRuntime.FROM_IMAGE,
162
+ layers: undefined,
163
+ }
164
+ : {
165
+ runtime: CDKRuntime.NODEJS_16_X,
166
+ code: Code.fromAsset(path.resolve(__dirname, "../support/bridge")),
167
+ handler: "bridge.handler",
168
+ layers: [],
169
+ }),
149
170
  architecture,
150
- code: Code.fromAsset(path.resolve(__dirname, "../support/bridge")),
151
- handler: "bridge.handler",
152
171
  functionName,
153
- runtime: CDKRuntime.NODEJS_16_X,
154
172
  memorySize,
155
173
  ephemeralStorageSize: diskSize,
156
174
  timeout,
157
175
  tracing,
158
176
  environment: props.environment,
159
- layers: [],
160
177
  logRetention,
161
178
  logRetentionRetryOptions: logRetention && { maxRetries: 100 },
162
179
  retryAttempts: 0,
@@ -185,17 +202,30 @@ export class Function extends CDKFunction {
185
202
  else {
186
203
  super(scope, id, {
187
204
  ...props,
205
+ ...(props.runtime === "container"
206
+ ? {
207
+ code: Code.fromAssetImage(props.handler, {
208
+ ...(architecture?.dockerPlatform
209
+ ? { platform: Platform.custom(architecture.dockerPlatform) }
210
+ : {}),
211
+ }),
212
+ handler: CDKHandler.FROM_IMAGE,
213
+ runtime: CDKRuntime.FROM_IMAGE,
214
+ layers: undefined,
215
+ }
216
+ : {
217
+ code: Code.fromInline("export function placeholder() {}"),
218
+ handler: "index.placeholder",
219
+ runtime: CDKRuntime.NODEJS_16_X,
220
+ layers: Function.buildLayers(scope, id, props),
221
+ }),
188
222
  architecture,
189
- code: Code.fromInline("export function placeholder() {}"),
190
- handler: "index.placeholder",
191
223
  functionName,
192
- runtime: CDKRuntime.NODEJS_16_X,
193
224
  memorySize,
194
225
  ephemeralStorageSize: diskSize,
195
226
  timeout,
196
227
  tracing,
197
228
  environment: props.environment,
198
- layers: Function.buildLayers(scope, id, props),
199
229
  logRetention,
200
230
  logRetentionRetryOptions: logRetention && { maxRetries: 100 },
201
231
  });
@@ -203,11 +233,14 @@ export class Function extends CDKFunction {
203
233
  // Build function
204
234
  const result = await useRuntimeHandlers().build(this.node.addr, "deploy");
205
235
  if (result.type === "error") {
206
- throw new Error([
236
+ throw new VisibleError([
207
237
  `Failed to build function "${props.handler}"`,
208
238
  ...result.errors,
209
239
  ].join("\n"));
210
240
  }
241
+ // No need to update code if runtime is container
242
+ if (props.runtime === "container")
243
+ return;
211
244
  // Update code
212
245
  const cfnFunction = this.node.defaultChild;
213
246
  const code = AssetCode.fromAsset(result.out);
@@ -5,7 +5,7 @@ import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam";
5
5
  import { RetentionDays } from "aws-cdk-lib/aws-logs";
6
6
  import { Code, Runtime, Architecture, Function as CdkFunction, FunctionUrlAuthType, } from "aws-cdk-lib/aws-lambda";
7
7
  import { Distribution, ViewerProtocolPolicy, AllowedMethods, CachedMethods, } from "aws-cdk-lib/aws-cloudfront";
8
- import { S3Origin, HttpOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
8
+ import { HttpOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
9
9
  import { Rule, Schedule } from "aws-cdk-lib/aws-events";
10
10
  import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
11
11
  import { Queue } from "aws-cdk-lib/aws-sqs";
@@ -29,7 +29,7 @@ import { toCdkSize } from "./util/size.js";
29
29
  export class NextjsSite extends SsrSite {
30
30
  constructor(scope, id, props) {
31
31
  super(scope, id, {
32
- buildCommand: "npx --yes open-next@2.0.3 build",
32
+ buildCommand: "npx --yes open-next@2.0.4 build",
33
33
  ...props,
34
34
  });
35
35
  this.deferredTaskCallbacks.push(() => {
@@ -282,9 +282,6 @@ export class NextjsSite extends SsrSite {
282
282
  createCloudFrontDistributionForEdge() {
283
283
  const { cdk } = this.props;
284
284
  const cfDistributionProps = cdk?.distribution || {};
285
- const s3Origin = new S3Origin(this.cdk.bucket, {
286
- originPath: "/" + this.buildConfig.clientBuildS3KeyPrefix,
287
- });
288
285
  const cachePolicy = cdk?.serverCachePolicy ??
289
286
  this.buildServerCachePolicy([
290
287
  "accept",
@@ -292,7 +289,7 @@ export class NextjsSite extends SsrSite {
292
289
  "next-router-prefetch",
293
290
  "next-router-state-tree",
294
291
  ]);
295
- const serverBehavior = this.buildDefaultBehaviorForEdge(s3Origin, cachePolicy);
292
+ const serverBehavior = this.buildDefaultBehaviorForEdge(cachePolicy);
296
293
  return new Distribution(this, "Distribution", {
297
294
  // these values can be overwritten by cfDistributionProps
298
295
  defaultRootObject: "",
@@ -4,7 +4,6 @@ import { IFunction as ICdkFunction, FunctionProps } from "aws-cdk-lib/aws-lambda
4
4
  import { IHostedZone } from "aws-cdk-lib/aws-route53";
5
5
  import { Distribution, ICachePolicy, IResponseHeadersPolicy, BehaviorOptions, CachePolicy, Function as CfFunction, FunctionEventType as CfFunctionEventType } from "aws-cdk-lib/aws-cloudfront";
6
6
  import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
7
- import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
8
7
  import { SSTConstruct } from "./Construct.js";
9
8
  import { NodeJSProps } from "./Function.js";
10
9
  import { SsrFunction } from "./SsrFunction.js";
@@ -224,6 +223,7 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
224
223
  private serverLambdaForDev?;
225
224
  protected bucket: Bucket;
226
225
  private cfFunction;
226
+ private s3Origin;
227
227
  private distribution;
228
228
  private hostedZone?;
229
229
  private certificate?;
@@ -286,12 +286,13 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
286
286
  private grantServerS3Permissions;
287
287
  private grantServerCloudFrontPermissions;
288
288
  private validateCloudFrontDistributionSettings;
289
+ private createCloudFrontS3Origin;
289
290
  private createCloudFrontFunction;
290
291
  protected createCloudFrontDistributionForRegional(): Distribution;
291
292
  protected createCloudFrontDistributionForEdge(): Distribution;
292
293
  protected buildDistributionDomainNames(): string[];
293
294
  protected buildDefaultBehaviorForRegional(cachePolicy: ICachePolicy): BehaviorOptions;
294
- protected buildDefaultBehaviorForEdge(origin: S3Origin, cachePolicy: ICachePolicy): BehaviorOptions;
295
+ protected buildDefaultBehaviorForEdge(cachePolicy: ICachePolicy): BehaviorOptions;
295
296
  protected buildBehaviorFunctionAssociations(): {
296
297
  eventType: CfFunctionEventType;
297
298
  function: CfFunction;
@@ -54,6 +54,7 @@ export class SsrSite extends Construct {
54
54
  serverLambdaForDev;
55
55
  bucket;
56
56
  cfFunction;
57
+ s3Origin;
57
58
  distribution;
58
59
  hostedZone;
59
60
  certificate;
@@ -79,7 +80,7 @@ export class SsrSite extends Construct {
79
80
  useSites().add(id, this.constructor.name, this.props);
80
81
  if (this.doNotDeploy) {
81
82
  // @ts-ignore
82
- this.cfFunction = this.bucket = this.distribution = null;
83
+ this.cfFunction = this.bucket = this.s3Origin = this.distribution = null;
83
84
  this.serverLambdaForDev = this.createFunctionForDev();
84
85
  return;
85
86
  }
@@ -103,6 +104,7 @@ export class SsrSite extends Construct {
103
104
  this.certificate = this.createCertificate();
104
105
  // Create CloudFront
105
106
  this.validateCloudFrontDistributionSettings();
107
+ this.s3Origin = this.createCloudFrontS3Origin();
106
108
  this.cfFunction = this.createCloudFrontFunction();
107
109
  this.distribution = this.props.edge
108
110
  ? this.createCloudFrontDistributionForEdge()
@@ -502,6 +504,11 @@ export class SsrSite extends Construct {
502
504
  throw new Error(`Do not configure the "cfDistribution.domainNames". Use the "customDomain" to configure the domain name.`);
503
505
  }
504
506
  }
507
+ createCloudFrontS3Origin() {
508
+ return new S3Origin(this.bucket, {
509
+ originPath: "/" + (this.buildConfig.clientBuildS3KeyPrefix ?? ""),
510
+ });
511
+ }
505
512
  createCloudFrontFunction() {
506
513
  return new CfFunction(this, "CloudFrontFunction", {
507
514
  code: CfFunctionCode.fromInline(`
@@ -534,7 +541,6 @@ function handler(event) {
534
541
  createCloudFrontDistributionForEdge() {
535
542
  const { cdk } = this.props;
536
543
  const cfDistributionProps = cdk?.distribution || {};
537
- const s3Origin = new S3Origin(this.bucket);
538
544
  const cachePolicy = cdk?.serverCachePolicy ?? this.buildServerCachePolicy();
539
545
  return new Distribution(this, "Distribution", {
540
546
  // these values can be overwritten by cfDistributionProps
@@ -544,7 +550,7 @@ function handler(event) {
544
550
  // these values can NOT be overwritten by cfDistributionProps
545
551
  domainNames: this.buildDistributionDomainNames(),
546
552
  certificate: this.certificate,
547
- defaultBehavior: this.buildDefaultBehaviorForEdge(s3Origin, cachePolicy),
553
+ defaultBehavior: this.buildDefaultBehaviorForEdge(cachePolicy),
548
554
  additionalBehaviors: {
549
555
  ...(cfDistributionProps.additionalBehaviors || {}),
550
556
  },
@@ -595,12 +601,12 @@ function handler(event) {
595
601
  ],
596
602
  };
597
603
  }
598
- buildDefaultBehaviorForEdge(origin, cachePolicy) {
604
+ buildDefaultBehaviorForEdge(cachePolicy) {
599
605
  const { cdk } = this.props;
600
606
  const cfDistributionProps = cdk?.distribution || {};
601
607
  return {
602
608
  viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
603
- origin,
609
+ origin: this.s3Origin,
604
610
  allowedMethods: AllowedMethods.ALLOW_ALL,
605
611
  cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
606
612
  compress: true,
@@ -636,9 +642,7 @@ function handler(event) {
636
642
  const publicDir = path.join(this.props.path, this.buildConfig.clientBuildOutputDir);
637
643
  for (const item of fs.readdirSync(publicDir)) {
638
644
  const isDir = fs.statSync(path.join(publicDir, item)).isDirectory();
639
- this.distribution.addBehavior(isDir ? `${item}/*` : item, new S3Origin(this.bucket, {
640
- originPath: "/" + (this.buildConfig.clientBuildS3KeyPrefix ?? ""),
641
- }), {
645
+ this.distribution.addBehavior(isDir ? `${item}/*` : item, this.s3Origin, {
642
646
  viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
643
647
  allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
644
648
  cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sideEffects": false,
3
3
  "name": "sst",
4
- "version": "2.18.3",
4
+ "version": "2.19.0",
5
5
  "bin": {
6
6
  "sst": "cli/sst.js"
7
7
  },
@@ -0,0 +1 @@
1
+ export declare const useContainerHandler: () => Promise<void>;
@@ -0,0 +1,124 @@
1
+ import { useRuntimeHandlers } from "../handlers.js";
2
+ import { useRuntimeWorkers } from "../workers.js";
3
+ import { Context } from "../../context/context.js";
4
+ import { VisibleError } from "../../error.js";
5
+ import { spawn } from "child_process";
6
+ import { useRuntimeServerConfig } from "../server.js";
7
+ import { isChild } from "../../util/fs.js";
8
+ import { execAsync } from "../../util/process.js";
9
+ export const useContainerHandler = Context.memo(async () => {
10
+ const workers = await useRuntimeWorkers();
11
+ const server = await useRuntimeServerConfig();
12
+ const handlers = useRuntimeHandlers();
13
+ const containers = new Map();
14
+ const sources = new Map();
15
+ handlers.register({
16
+ shouldBuild: (input) => {
17
+ const parent = sources.get(input.functionID);
18
+ if (!parent)
19
+ return false;
20
+ return isChild(parent, input.file);
21
+ },
22
+ canHandle: (input) => input.startsWith("container"),
23
+ startWorker: async (input) => {
24
+ const name = `sst-workerID-${input.workerID}-${Date.now()}`;
25
+ const proc = spawn("docker", [
26
+ "run",
27
+ "--rm",
28
+ "--network=host",
29
+ `--name=${name}`,
30
+ ...Object.entries({
31
+ ...input.environment,
32
+ IS_LOCAL: "true",
33
+ AWS_LAMBDA_RUNTIME_API: `host.docker.internal:${server.port}/${input.workerID}`,
34
+ })
35
+ .map(([key, value]) => ["-e", `${key}=${value}`])
36
+ .flat(),
37
+ `sst-dev:${input.functionID}`,
38
+ ], {
39
+ env: {
40
+ ...process.env,
41
+ },
42
+ cwd: input.out,
43
+ });
44
+ proc.on("exit", () => {
45
+ workers.exited(input.workerID);
46
+ });
47
+ proc.stdout.on("data", (data) => {
48
+ workers.stdout(input.workerID, data.toString());
49
+ });
50
+ proc.stderr.on("data", (data) => {
51
+ workers.stdout(input.workerID, data.toString());
52
+ });
53
+ containers.set(input.workerID, name);
54
+ },
55
+ stopWorker: async (workerID) => {
56
+ const name = containers.get(workerID);
57
+ if (name) {
58
+ try {
59
+ // note:
60
+ // - calling `docker kill` kills the docker process much faster than `docker stop`
61
+ // - process.kill() does not work on docker processes
62
+ await execAsync(`docker kill ${name}`, {
63
+ env: {
64
+ ...process.env,
65
+ },
66
+ });
67
+ }
68
+ catch (ex) {
69
+ console.error(ex);
70
+ throw new VisibleError(`Could not stop docker container ${name}`);
71
+ }
72
+ containers.delete(workerID);
73
+ }
74
+ },
75
+ build: async (input) => {
76
+ const project = input.props.handler;
77
+ sources.set(input.functionID, project);
78
+ if (input.mode === "start") {
79
+ try {
80
+ const result = await execAsync(`docker build -t sst-dev:${input.functionID} .`, {
81
+ cwd: project,
82
+ env: {
83
+ ...process.env,
84
+ },
85
+ });
86
+ }
87
+ catch (ex) {
88
+ return {
89
+ type: "error",
90
+ errors: [String(ex)],
91
+ };
92
+ }
93
+ }
94
+ if (input.mode === "deploy") {
95
+ try {
96
+ const platform = input.props.architecture === "arm_64"
97
+ ? "linux/arm64"
98
+ : "linux/amd64";
99
+ await execAsync([
100
+ `docker build`,
101
+ `-t sst-build:${input.functionID}`,
102
+ `--platform ${platform}`,
103
+ `.`,
104
+ ].join(" "), {
105
+ cwd: project,
106
+ env: {
107
+ ...process.env,
108
+ },
109
+ });
110
+ }
111
+ catch (ex) {
112
+ return {
113
+ type: "error",
114
+ errors: [String(ex)],
115
+ };
116
+ }
117
+ }
118
+ return {
119
+ type: "success",
120
+ handler: "not required for container",
121
+ };
122
+ },
123
+ });
124
+ });
@@ -177,7 +177,7 @@ export const useNodeHandler = Context.memo(async () => {
177
177
  const json = JSON.parse(await fs
178
178
  .readFile(path.join(src, "package.json"))
179
179
  .then((x) => x.toString()));
180
- fs.writeFile(path.join(input.out, "package.json"), JSON.stringify({
180
+ await fs.writeFile(path.join(input.out, "package.json"), JSON.stringify({
181
181
  dependencies: Object.fromEntries(installPackages.map((x) => [x, json.dependencies?.[x] || "*"])),
182
182
  }));
183
183
  const cmd = ["npm install"];
@@ -17,6 +17,7 @@ const RUNTIME_MAP = {
17
17
  "python3.7": Runtime.PYTHON_3_7,
18
18
  "python3.8": Runtime.PYTHON_3_8,
19
19
  "python3.9": Runtime.PYTHON_3_9,
20
+ "python3.10": Runtime.PYTHON_3_10,
20
21
  };
21
22
  export const usePythonHandler = Context.memo(async () => {
22
23
  const workers = await useRuntimeWorkers();
@@ -1,6 +1,9 @@
1
1
  import { FunctionProps } from "../constructs/Function.js";
2
2
  declare module "../bus.js" {
3
3
  interface Events {
4
+ "function.build.started": {
5
+ functionID: string;
6
+ };
4
7
  "function.build.success": {
5
8
  functionID: string;
6
9
  };
@@ -19,6 +22,7 @@ interface BuildInput {
19
22
  interface StartWorkerInput {
20
23
  url: string;
21
24
  workerID: string;
25
+ functionID: string;
22
26
  environment: Record<string, string>;
23
27
  out: string;
24
28
  handler: string;
@@ -34,6 +34,7 @@ export const useRuntimeHandlers = Context.memo(() => {
34
34
  const out = path.join(project.paths.artifacts, functionID);
35
35
  await fs.rm(out, { recursive: true, force: true });
36
36
  await fs.mkdir(out, { recursive: true });
37
+ bus.publish("function.build.started", { functionID });
37
38
  if (func.hooks?.beforeBuild)
38
39
  await func.hooks.beforeBuild(func, out);
39
40
  const built = await handler.build({
@@ -43,6 +43,7 @@ export const useRuntimeWorkers = Context.memo(async () => {
43
43
  await handler.startWorker({
44
44
  ...build,
45
45
  workerID: evt.properties.workerID,
46
+ functionID: evt.properties.functionID,
46
47
  environment: evt.properties.env,
47
48
  url: `${server.url}/${evt.properties.workerID}/${server.API_VERSION}`,
48
49
  runtime: props.runtime,