sst 2.0.0-rc.24 → 2.0.0-rc.26
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.d.ts +2 -4
- package/bootstrap.js +74 -35
- package/cli/ui/deploy.js +2 -0
- package/constructs/App.js +9 -1
- package/constructs/Stack.d.ts +1 -3
- package/constructs/Stack.js +7 -28
- package/package.json +1 -1
- package/site-env.js +1 -1
- package/sst.mjs +204 -163
- package/support/bootstrap-metadata-function/index.mjs +30820 -0
- package/support/custom-resources/index.mjs +69 -33512
package/bootstrap.d.ts
CHANGED
package/bootstrap.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import url from "url";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { DescribeStacksCommand, CloudFormationClient, } from "@aws-sdk/client-cloudformation";
|
|
4
|
+
import { Tags, Stack, RemovalPolicy, App, CfnOutput } from "aws-cdk-lib";
|
|
5
|
+
import { Function, Runtime, Code } from "aws-cdk-lib/aws-lambda";
|
|
6
|
+
import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
|
7
|
+
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
8
|
+
import { Queue } from "aws-cdk-lib/aws-sqs";
|
|
9
|
+
import { Rule } from "aws-cdk-lib/aws-events";
|
|
10
|
+
import { SqsQueue } from "aws-cdk-lib/aws-events-targets";
|
|
3
11
|
import { BlockPublicAccess, Bucket, BucketEncryption, } from "aws-cdk-lib/aws-s3";
|
|
4
|
-
import { ParameterTier, StringParameter } from "aws-cdk-lib/aws-ssm";
|
|
5
12
|
import { useProject } from "./project.js";
|
|
6
13
|
import { createSpinner } from "./cli/spinner.js";
|
|
7
14
|
import { Context } from "./context/context.js";
|
|
@@ -9,71 +16,103 @@ import { useAWSClient } from "./credentials.js";
|
|
|
9
16
|
import { VisibleError } from "./error.js";
|
|
10
17
|
import { Logger } from "./logger.js";
|
|
11
18
|
import { Stacks } from "./stacks/index.js";
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
19
|
+
const STACK_NAME = "SSTBootstrap";
|
|
20
|
+
const OUTPUT_VERSION = "Version";
|
|
21
|
+
const OUTPUT_BUCKET = "BucketName";
|
|
22
|
+
const LATEST_VERSION = "4";
|
|
23
|
+
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
16
24
|
export const useBootstrap = Context.memo(async () => {
|
|
17
25
|
Logger.debug("Initializing bootstrap context");
|
|
18
|
-
const ret = await
|
|
26
|
+
const ret = await loadBootstrapStatus();
|
|
19
27
|
if (!ret.version ||
|
|
20
28
|
!ret.bucket ||
|
|
21
|
-
!ret.stack ||
|
|
22
29
|
ret.version !== LATEST_VERSION) {
|
|
23
30
|
const project = useProject();
|
|
24
31
|
const spinner = createSpinner("Deploying bootstrap stack, this only needs to happen once").start();
|
|
32
|
+
// Create bootstrap stack
|
|
25
33
|
const app = new App();
|
|
26
|
-
const stack = new Stack(app,
|
|
34
|
+
const stack = new Stack(app, STACK_NAME, {
|
|
27
35
|
env: {
|
|
28
36
|
region: project.config.region,
|
|
29
37
|
},
|
|
30
38
|
});
|
|
39
|
+
// Add tags to stack
|
|
31
40
|
const tags = {};
|
|
32
41
|
for (const [key, value] of Object.entries(tags)) {
|
|
33
42
|
Tags.of(app).add(key, value);
|
|
34
43
|
}
|
|
44
|
+
// Create S3 bucket to store stacks metadata
|
|
35
45
|
const bucket = new Bucket(stack, project.config.region, {
|
|
36
46
|
encryption: BucketEncryption.S3_MANAGED,
|
|
37
47
|
removalPolicy: RemovalPolicy.DESTROY,
|
|
38
48
|
autoDeleteObjects: true,
|
|
39
49
|
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
|
|
40
50
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
// Create Function and subscribe to CloudFormation events
|
|
52
|
+
const fn = new Function(stack, "MetadataHandler", {
|
|
53
|
+
code: Code.fromAsset(path.resolve(__dirname, "support/bootstrap-metadata-function")),
|
|
54
|
+
handler: "index.handler",
|
|
55
|
+
runtime: Runtime.NODEJS_16_X,
|
|
56
|
+
initialPolicy: [
|
|
57
|
+
new PolicyStatement({
|
|
58
|
+
actions: ["cloudformation:DescribeStacks"],
|
|
59
|
+
resources: ["*"],
|
|
60
|
+
}),
|
|
61
|
+
new PolicyStatement({
|
|
62
|
+
actions: ["s3:PutObject", "s3:DeleteObject"],
|
|
63
|
+
resources: [bucket.bucketArn + "/*"],
|
|
64
|
+
}),
|
|
65
|
+
],
|
|
45
66
|
});
|
|
46
|
-
new
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
67
|
+
const queue = new Queue(stack, "MetadataQueue");
|
|
68
|
+
fn.addEventSource(new SqsEventSource(queue));
|
|
69
|
+
const rule = new Rule(stack, "MetadataRule", {
|
|
70
|
+
eventPattern: {
|
|
71
|
+
source: ["aws.cloudformation"],
|
|
72
|
+
detailType: ["CloudFormation Stack Status Change"],
|
|
73
|
+
detail: {
|
|
74
|
+
"status-details": {
|
|
75
|
+
status: [
|
|
76
|
+
"CREATE_COMPLETE",
|
|
77
|
+
"UPDATE_COMPLETE",
|
|
78
|
+
"UPDATE_ROLLBACK_COMPLETE",
|
|
79
|
+
"ROLLBACK_COMPLETE",
|
|
80
|
+
"DELETE_COMPLETE",
|
|
81
|
+
],
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
55
85
|
});
|
|
86
|
+
rule.addTarget(new SqsQueue(queue, {
|
|
87
|
+
retryAttempts: 10,
|
|
88
|
+
}));
|
|
89
|
+
// Create stack outputs to store bootstrap stack info
|
|
90
|
+
new CfnOutput(stack, OUTPUT_VERSION, { value: LATEST_VERSION });
|
|
91
|
+
new CfnOutput(stack, OUTPUT_BUCKET, { value: bucket.bucketName });
|
|
56
92
|
const asm = app.synth();
|
|
57
93
|
const result = await Stacks.deploy(asm.stacks[0]);
|
|
58
94
|
if (Stacks.isFailed(result.status)) {
|
|
59
95
|
throw new VisibleError(`Failed to deploy bootstrap stack:\n${JSON.stringify(result.errors, null, 4)}`);
|
|
60
96
|
}
|
|
61
97
|
spinner.succeed();
|
|
62
|
-
return
|
|
98
|
+
return loadBootstrapStatus();
|
|
63
99
|
}
|
|
64
100
|
Logger.debug("Loaded bootstrap info: ", JSON.stringify(ret));
|
|
65
101
|
return ret;
|
|
66
102
|
});
|
|
67
|
-
async function
|
|
68
|
-
const
|
|
69
|
-
const result = await
|
|
70
|
-
|
|
103
|
+
async function loadBootstrapStatus() {
|
|
104
|
+
const cf = useAWSClient(CloudFormationClient);
|
|
105
|
+
const result = await cf.send(new DescribeStacksCommand({
|
|
106
|
+
StackName: STACK_NAME,
|
|
71
107
|
}));
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
108
|
+
let version, bucket;
|
|
109
|
+
(result.Stacks[0].Outputs || []).forEach((o) => {
|
|
110
|
+
if (o.OutputKey === OUTPUT_VERSION) {
|
|
111
|
+
version = o.OutputValue;
|
|
112
|
+
}
|
|
113
|
+
else if (o.OutputKey === OUTPUT_BUCKET) {
|
|
114
|
+
bucket = o.OutputValue;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return { version, bucket };
|
|
79
118
|
}
|
package/cli/ui/deploy.js
CHANGED
package/constructs/App.js
CHANGED
|
@@ -304,7 +304,15 @@ export class App extends cdk.App {
|
|
|
304
304
|
for (const child of this.node.children) {
|
|
305
305
|
if (child instanceof Stack) {
|
|
306
306
|
const stackName = child.node.id;
|
|
307
|
-
child.
|
|
307
|
+
child.addOutputs({
|
|
308
|
+
SstMetadata: JSON.stringify({
|
|
309
|
+
app: this.name,
|
|
310
|
+
stage: this.stage,
|
|
311
|
+
version: useProject().version,
|
|
312
|
+
bootstrapBucket: this.bootstrap.bucket,
|
|
313
|
+
metadata: byStack[stackName] || [],
|
|
314
|
+
}),
|
|
315
|
+
});
|
|
308
316
|
}
|
|
309
317
|
}
|
|
310
318
|
}
|
package/constructs/Stack.d.ts
CHANGED
|
@@ -32,7 +32,6 @@ export declare class Stack extends cdk.Stack {
|
|
|
32
32
|
* @internal
|
|
33
33
|
*/
|
|
34
34
|
readonly customResourceHandler: lambda.Function;
|
|
35
|
-
private readonly metadata;
|
|
36
35
|
constructor(scope: Construct, id: string, props?: StackProps);
|
|
37
36
|
/**
|
|
38
37
|
* The default function props to be applied to all the Lambda functions in the stack.
|
|
@@ -129,9 +128,8 @@ export declare class Stack extends cdk.Stack {
|
|
|
129
128
|
* ```
|
|
130
129
|
*/
|
|
131
130
|
addOutputs(outputs: Record<string, string | cdk.CfnOutputProps>): void;
|
|
132
|
-
setStackMetadata(metadata: any): void;
|
|
133
131
|
private createCustomResourceHandler;
|
|
134
|
-
private
|
|
132
|
+
private createSecretsMigrationResource;
|
|
135
133
|
private static checkForPropsIsConstruct;
|
|
136
134
|
private static checkForEnvInProps;
|
|
137
135
|
}
|
package/constructs/Stack.js
CHANGED
|
@@ -5,10 +5,8 @@ import * as iam from "aws-cdk-lib/aws-iam";
|
|
|
5
5
|
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
6
6
|
import { Function as Fn } from "./Function.js";
|
|
7
7
|
import { isConstruct } from "./Construct.js";
|
|
8
|
-
import { createRequire } from "module";
|
|
9
8
|
import { useApp } from "./context.js";
|
|
10
9
|
import { useProject } from "../project.js";
|
|
11
|
-
const require = createRequire(import.meta.url);
|
|
12
10
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
13
11
|
/**
|
|
14
12
|
* The Stack construct extends cdk.Stack. It automatically prefixes the stack names with the stage and app name to ensure that they can be deployed to multiple regions in the same AWS account. It also ensure that the stack uses the same AWS profile and region as the app. They're defined using functions that return resources that can be imported by other stacks.
|
|
@@ -36,7 +34,6 @@ export class Stack extends cdk.Stack {
|
|
|
36
34
|
* @internal
|
|
37
35
|
*/
|
|
38
36
|
customResourceHandler;
|
|
39
|
-
metadata;
|
|
40
37
|
constructor(scope, id, props) {
|
|
41
38
|
const root = scope.node.root;
|
|
42
39
|
const stackId = root.logicalPrefixedName(id);
|
|
@@ -51,15 +48,10 @@ export class Stack extends cdk.Stack {
|
|
|
51
48
|
});
|
|
52
49
|
this.stage = root.stage;
|
|
53
50
|
this.defaultFunctionProps = root.defaultFunctionProps.map((dfp) => typeof dfp === "function" ? dfp(this) : dfp);
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
// We do this two step process because we call the "Template.fromStack"
|
|
57
|
-
// method in the tests. And the call triggers an app synethsis. And we
|
|
58
|
-
// end up synthesize an app multiple times. If we created the Metadata
|
|
59
|
-
// resource on app synth, the tests would fail because the resource
|
|
60
|
-
// would already exist.
|
|
51
|
+
// Create a custom resource handler per stack. This handler will
|
|
52
|
+
// be used by all the custom resources in the stack.
|
|
61
53
|
this.customResourceHandler = this.createCustomResourceHandler();
|
|
62
|
-
this.
|
|
54
|
+
this.createSecretsMigrationResource();
|
|
63
55
|
}
|
|
64
56
|
/**
|
|
65
57
|
* The default function props to be applied to all the Lambda functions in the stack.
|
|
@@ -200,9 +192,6 @@ export class Stack extends cdk.Stack {
|
|
|
200
192
|
}
|
|
201
193
|
});
|
|
202
194
|
}
|
|
203
|
-
setStackMetadata(metadata) {
|
|
204
|
-
this.metadata.node.defaultChild.addPropertyOverride("Metadata", metadata);
|
|
205
|
-
}
|
|
206
195
|
createCustomResourceHandler() {
|
|
207
196
|
return new lambda.Function(this, "CustomResourceHandler", {
|
|
208
197
|
code: lambda.Code.fromAsset(path.join(__dirname, "../support/custom-resources/"), {
|
|
@@ -214,14 +203,9 @@ export class Stack extends cdk.Stack {
|
|
|
214
203
|
memorySize: 1024,
|
|
215
204
|
});
|
|
216
205
|
}
|
|
217
|
-
|
|
206
|
+
createSecretsMigrationResource() {
|
|
218
207
|
const app = useApp();
|
|
219
|
-
//
|
|
220
|
-
this.customResourceHandler.addToRolePolicy(new iam.PolicyStatement({
|
|
221
|
-
actions: ["s3:PutObject", "s3:DeleteObject"],
|
|
222
|
-
resources: [`arn:aws:s3:::${app.bootstrap.bucket}/*`],
|
|
223
|
-
}));
|
|
224
|
-
// Temporary: Add permissions to migrate SSM paths for secrets (piggybacking on the stack metadata custom resource handler)
|
|
208
|
+
// Add permissions to migrate SSM paths for secrets
|
|
225
209
|
this.customResourceHandler.addToRolePolicy(new iam.PolicyStatement({
|
|
226
210
|
actions: ["ssm:GetParametersByPath", "ssm:PutParameter"],
|
|
227
211
|
resources: [
|
|
@@ -229,18 +213,13 @@ export class Stack extends cdk.Stack {
|
|
|
229
213
|
`arn:aws:ssm:${app.region}:${app.account}:parameter/sst/${app.name}/.fallback/*`,
|
|
230
214
|
],
|
|
231
215
|
}));
|
|
232
|
-
|
|
216
|
+
new cdk.CustomResource(this, "SecretsMigration", {
|
|
233
217
|
serviceToken: this.customResourceHandler.functionArn,
|
|
234
|
-
resourceType: "Custom::
|
|
218
|
+
resourceType: "Custom::SecretsMigration",
|
|
235
219
|
properties: {
|
|
236
220
|
App: app.name,
|
|
237
221
|
Stage: this.stage,
|
|
238
|
-
Stack: this.stackName,
|
|
239
222
|
SSTVersion: useProject().version,
|
|
240
|
-
BootstrapBucketName: app.bootstrap.bucket,
|
|
241
|
-
ForceUpdate: process.env.SST_FORCE_UPDATE_METADATA
|
|
242
|
-
? Date.now().toString()
|
|
243
|
-
: undefined,
|
|
244
223
|
},
|
|
245
224
|
});
|
|
246
225
|
}
|
package/package.json
CHANGED
package/site-env.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function writeValues(input) {
|
|
|
37
37
|
await fs.promises.writeFile(file, JSON.stringify(input));
|
|
38
38
|
}
|
|
39
39
|
export function append(input) {
|
|
40
|
-
input.path = path.
|
|
40
|
+
input.path = path.resolve(useProject().paths.root, input.path);
|
|
41
41
|
fs.appendFileSync(keysFile(), JSON.stringify(input) + "\n");
|
|
42
42
|
}
|
|
43
43
|
export function reset() {
|