puls-dev 0.1.7 → 0.1.9
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 +10 -8
- package/dist/core/checker.d.ts +1 -1
- package/dist/core/checker.js +88 -56
- package/dist/core/decorators.js +8 -2
- package/dist/core/resource.js +2 -2
- package/dist/core/stack.d.ts +1 -1
- package/dist/core/stack.js +2 -2
- package/dist/providers/aws/acm.d.ts +1 -1
- package/dist/providers/aws/acm.js +27 -23
- package/dist/providers/aws/api.d.ts +14 -14
- package/dist/providers/aws/api.js +21 -21
- package/dist/providers/aws/apigateway.d.ts +2 -2
- package/dist/providers/aws/apigateway.js +33 -29
- package/dist/providers/aws/cloudfront.d.ts +3 -3
- package/dist/providers/aws/cloudfront.js +49 -34
- package/dist/providers/aws/fargate.d.ts +2 -2
- package/dist/providers/aws/fargate.js +99 -52
- package/dist/providers/aws/lambda.d.ts +2 -2
- package/dist/providers/aws/lambda.js +63 -32
- package/dist/providers/aws/rds.d.ts +1 -1
- package/dist/providers/aws/rds.js +77 -39
- package/dist/providers/aws/route53.d.ts +5 -5
- package/dist/providers/aws/route53.js +42 -35
- package/dist/providers/aws/s3.d.ts +2 -2
- package/dist/providers/aws/s3.js +40 -33
- package/dist/providers/aws/secrets.js +15 -7
- package/dist/providers/aws/sqs.d.ts +1 -1
- package/dist/providers/aws/sqs.js +47 -23
- package/dist/providers/do/domain.d.ts +4 -4
- package/dist/providers/do/domain.js +15 -11
- package/dist/providers/firebase/auth.d.ts +1 -1
- package/dist/providers/firebase/auth.js +65 -33
- package/dist/providers/firebase/firestore.d.ts +2 -2
- package/dist/providers/firebase/firestore.js +45 -28
- package/dist/providers/firebase/functions.d.ts +1 -1
- package/dist/providers/firebase/functions.js +75 -42
- package/dist/providers/firebase/hosting.d.ts +3 -1
- package/dist/providers/firebase/hosting.js +102 -56
- package/dist/providers/firebase/remoteconfig.d.ts +1 -1
- package/dist/providers/firebase/remoteconfig.js +42 -33
- package/dist/providers/firebase/storage.d.ts +1 -1
- package/dist/providers/firebase/storage.js +38 -24
- package/dist/providers/proxmox/vm.js +34 -22
- package/dist/types/aws.js +1 -1
- package/package.json +2 -1
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import { DescribeClustersCommand, CreateClusterCommand, DescribeServicesCommand, CreateServiceCommand, UpdateServiceCommand, DeleteServiceCommand, RegisterTaskDefinitionCommand, NetworkMode, LaunchType, SchedulingStrategy, } from
|
|
2
|
-
import { DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, CreateSecurityGroupCommand, AuthorizeSecurityGroupIngressCommand, } from
|
|
3
|
-
import { GetRoleCommand, CreateRoleCommand, AttachRolePolicyCommand, } from
|
|
4
|
-
import { DescribeLogGroupsCommand, CreateLogGroupCommand, } from
|
|
5
|
-
import { BaseBuilder } from
|
|
6
|
-
import { getECSClient, getEC2Client, getIAMClient, getCWLogsClient } from
|
|
7
|
-
import { resolveEnvVars } from
|
|
8
|
-
import { Config } from
|
|
1
|
+
import { DescribeClustersCommand, CreateClusterCommand, DescribeServicesCommand, CreateServiceCommand, UpdateServiceCommand, DeleteServiceCommand, RegisterTaskDefinitionCommand, NetworkMode, LaunchType, SchedulingStrategy, } from "@aws-sdk/client-ecs";
|
|
2
|
+
import { DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, CreateSecurityGroupCommand, AuthorizeSecurityGroupIngressCommand, } from "@aws-sdk/client-ec2";
|
|
3
|
+
import { GetRoleCommand, CreateRoleCommand, AttachRolePolicyCommand, } from "@aws-sdk/client-iam";
|
|
4
|
+
import { DescribeLogGroupsCommand, CreateLogGroupCommand, } from "@aws-sdk/client-cloudwatch-logs";
|
|
5
|
+
import { BaseBuilder } from "../../core/resource.js";
|
|
6
|
+
import { getECSClient, getEC2Client, getIAMClient, getCWLogsClient, } from "./api.js";
|
|
7
|
+
import { resolveEnvVars } from "./secrets.js";
|
|
8
|
+
import { Config } from "../../core/config.js";
|
|
9
9
|
const ECS_ASSUME_ROLE_POLICY = JSON.stringify({
|
|
10
|
-
Version:
|
|
11
|
-
Statement: [
|
|
10
|
+
Version: "2012-10-17",
|
|
11
|
+
Statement: [
|
|
12
|
+
{
|
|
13
|
+
Effect: "Allow",
|
|
14
|
+
Principal: { Service: "ecs-tasks.amazonaws.com" },
|
|
15
|
+
Action: "sts:AssumeRole",
|
|
16
|
+
},
|
|
17
|
+
],
|
|
12
18
|
});
|
|
13
19
|
export class FargateBuilder extends BaseBuilder {
|
|
14
20
|
_image;
|
|
@@ -17,7 +23,7 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
17
23
|
_port;
|
|
18
24
|
_replicas = 1;
|
|
19
25
|
_env = {};
|
|
20
|
-
_clusterName =
|
|
26
|
+
_clusterName = "puls";
|
|
21
27
|
_subnetIds;
|
|
22
28
|
_securityGroupIds;
|
|
23
29
|
resolvedArn = null;
|
|
@@ -25,33 +31,61 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
25
31
|
super(name);
|
|
26
32
|
this.discoveryPromise = this.discoverService(name);
|
|
27
33
|
}
|
|
28
|
-
image(img) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
image(img) {
|
|
35
|
+
this._image = img;
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
cpu(units) {
|
|
39
|
+
this._cpu = units;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
memory(mb) {
|
|
43
|
+
this._memory = mb;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
port(p) {
|
|
47
|
+
this._port = p;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
replicas(n) {
|
|
51
|
+
this._replicas = n;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
cluster(name) {
|
|
55
|
+
this._clusterName = name;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
subnets(ids) {
|
|
59
|
+
this._subnetIds = ids;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
securityGroups(ids) {
|
|
63
|
+
this._securityGroupIds = ids;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
env(vars) {
|
|
67
|
+
this._env = { ...this._env, ...vars };
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
37
70
|
async discoverService(name) {
|
|
38
71
|
try {
|
|
39
72
|
const ecs = getECSClient();
|
|
40
73
|
const clusterDesc = await ecs.send(new DescribeClustersCommand({ clusters: [this._clusterName] }));
|
|
41
|
-
const cluster = clusterDesc.clusters?.find(c => c.status ===
|
|
74
|
+
const cluster = clusterDesc.clusters?.find((c) => c.status === "ACTIVE");
|
|
42
75
|
if (!cluster)
|
|
43
76
|
return null;
|
|
44
77
|
const result = await ecs.send(new DescribeServicesCommand({
|
|
45
78
|
cluster: this._clusterName,
|
|
46
79
|
services: [name],
|
|
47
80
|
}));
|
|
48
|
-
const svc = result.services?.find(s => s.status !==
|
|
81
|
+
const svc = result.services?.find((s) => s.status !== "INACTIVE");
|
|
49
82
|
if (svc)
|
|
50
83
|
this.resolvedArn = svc.serviceArn ?? null;
|
|
51
84
|
return svc ?? null;
|
|
52
85
|
}
|
|
53
86
|
catch (e) {
|
|
54
|
-
if (e.name ===
|
|
87
|
+
if (e.name === "ClusterNotFoundException" ||
|
|
88
|
+
e.name === "CredentialsProviderError")
|
|
55
89
|
return null;
|
|
56
90
|
throw e;
|
|
57
91
|
}
|
|
@@ -59,7 +93,7 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
59
93
|
async ensureCluster() {
|
|
60
94
|
const ecs = getECSClient();
|
|
61
95
|
const desc = await ecs.send(new DescribeClustersCommand({ clusters: [this._clusterName] }));
|
|
62
|
-
const existing = desc.clusters?.find(c => c.status ===
|
|
96
|
+
const existing = desc.clusters?.find((c) => c.status === "ACTIVE");
|
|
63
97
|
if (existing)
|
|
64
98
|
return existing.clusterArn;
|
|
65
99
|
const created = await ecs.send(new CreateClusterCommand({ clusterName: this._clusterName }));
|
|
@@ -74,7 +108,7 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
74
108
|
return existing.Role.Arn;
|
|
75
109
|
}
|
|
76
110
|
catch (e) {
|
|
77
|
-
if (e.name !==
|
|
111
|
+
if (e.name !== "NoSuchEntityException")
|
|
78
112
|
throw e;
|
|
79
113
|
}
|
|
80
114
|
const created = await iam.send(new CreateRoleCommand({
|
|
@@ -84,7 +118,7 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
84
118
|
}));
|
|
85
119
|
await iam.send(new AttachRolePolicyCommand({
|
|
86
120
|
RoleName: roleName,
|
|
87
|
-
PolicyArn:
|
|
121
|
+
PolicyArn: "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
|
|
88
122
|
}));
|
|
89
123
|
console.log(` ✅ Created execution role: ${roleName}`);
|
|
90
124
|
return created.Role.Arn;
|
|
@@ -93,7 +127,7 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
93
127
|
const logGroupName = `/puls/${this.name}`;
|
|
94
128
|
const cw = getCWLogsClient();
|
|
95
129
|
const existing = await cw.send(new DescribeLogGroupsCommand({ logGroupNamePrefix: logGroupName }));
|
|
96
|
-
if (existing.logGroups?.some(g => g.logGroupName === logGroupName))
|
|
130
|
+
if (existing.logGroups?.some((g) => g.logGroupName === logGroupName))
|
|
97
131
|
return logGroupName;
|
|
98
132
|
await cw.send(new CreateLogGroupCommand({ logGroupName }));
|
|
99
133
|
console.log(` ✅ Created log group: ${logGroupName}`);
|
|
@@ -102,15 +136,17 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
102
136
|
async discoverDefaultVpc() {
|
|
103
137
|
const ec2 = getEC2Client();
|
|
104
138
|
const vpcs = await ec2.send(new DescribeVpcsCommand({
|
|
105
|
-
Filters: [{ Name:
|
|
139
|
+
Filters: [{ Name: "isDefault", Values: ["true"] }],
|
|
106
140
|
}));
|
|
107
141
|
const vpcId = vpcs.Vpcs?.[0]?.VpcId;
|
|
108
142
|
if (!vpcId)
|
|
109
143
|
throw new Error(`[Fargate:${this.name}] No default VPC found. Use .subnets(ids[]) to specify subnets.`);
|
|
110
144
|
const subnets = await ec2.send(new DescribeSubnetsCommand({
|
|
111
|
-
Filters: [{ Name:
|
|
145
|
+
Filters: [{ Name: "vpc-id", Values: [vpcId] }],
|
|
112
146
|
}));
|
|
113
|
-
const subnetIds = (subnets.Subnets ?? [])
|
|
147
|
+
const subnetIds = (subnets.Subnets ?? [])
|
|
148
|
+
.map((s) => s.SubnetId)
|
|
149
|
+
.filter(Boolean);
|
|
114
150
|
return { vpcId, subnetIds };
|
|
115
151
|
}
|
|
116
152
|
async ensureSecurityGroup(vpcId) {
|
|
@@ -118,8 +154,8 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
118
154
|
const sgName = `puls-${this.name}-sg`;
|
|
119
155
|
const existing = await ec2.send(new DescribeSecurityGroupsCommand({
|
|
120
156
|
Filters: [
|
|
121
|
-
{ Name:
|
|
122
|
-
{ Name:
|
|
157
|
+
{ Name: "group-name", Values: [sgName] },
|
|
158
|
+
{ Name: "vpc-id", Values: [vpcId] },
|
|
123
159
|
],
|
|
124
160
|
}));
|
|
125
161
|
if (existing.SecurityGroups?.length)
|
|
@@ -133,13 +169,15 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
133
169
|
if (this._port) {
|
|
134
170
|
await ec2.send(new AuthorizeSecurityGroupIngressCommand({
|
|
135
171
|
GroupId: sgId,
|
|
136
|
-
IpPermissions: [
|
|
137
|
-
|
|
172
|
+
IpPermissions: [
|
|
173
|
+
{
|
|
174
|
+
IpProtocol: "tcp",
|
|
138
175
|
FromPort: this._port,
|
|
139
176
|
ToPort: this._port,
|
|
140
|
-
IpRanges: [{ CidrIp:
|
|
141
|
-
Ipv6Ranges: [{ CidrIpv6:
|
|
142
|
-
}
|
|
177
|
+
IpRanges: [{ CidrIp: "0.0.0.0/0" }],
|
|
178
|
+
Ipv6Ranges: [{ CidrIpv6: "::/0" }],
|
|
179
|
+
},
|
|
180
|
+
],
|
|
143
181
|
}));
|
|
144
182
|
}
|
|
145
183
|
console.log(` ✅ Created security group: ${sgName} (${sgId})`);
|
|
@@ -147,10 +185,13 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
147
185
|
}
|
|
148
186
|
async resolveNetworking() {
|
|
149
187
|
if (this._subnetIds && this._securityGroupIds) {
|
|
150
|
-
return {
|
|
188
|
+
return {
|
|
189
|
+
subnetIds: this._subnetIds,
|
|
190
|
+
securityGroupIds: this._securityGroupIds,
|
|
191
|
+
};
|
|
151
192
|
}
|
|
152
193
|
const { vpcId, subnetIds } = await this.discoverDefaultVpc();
|
|
153
|
-
const sgId = this._securityGroupIds?.[0] ?? await this.ensureSecurityGroup(vpcId);
|
|
194
|
+
const sgId = this._securityGroupIds?.[0] ?? (await this.ensureSecurityGroup(vpcId));
|
|
154
195
|
return {
|
|
155
196
|
subnetIds: this._subnetIds ?? subnetIds,
|
|
156
197
|
securityGroupIds: [sgId],
|
|
@@ -159,19 +200,19 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
159
200
|
async deploy() {
|
|
160
201
|
const dryRun = this.isDryRunActive();
|
|
161
202
|
const existing = await this.discoveryPromise;
|
|
162
|
-
const region = Config.get().providers.aws?.region ??
|
|
203
|
+
const region = Config.get().providers.aws?.region ?? "us-east-1";
|
|
163
204
|
console.log(`\n⚡ Finalizing Fargate Service "${this.name}"...`);
|
|
164
205
|
if (!this._image)
|
|
165
206
|
throw new Error(`[Fargate:${this.name}] .image("...") is required`);
|
|
166
207
|
if (dryRun) {
|
|
167
|
-
console.log(` 📝 [PLAN] ${existing ?
|
|
208
|
+
console.log(` 📝 [PLAN] ${existing ? "Update" : "Create"} Fargate service "${this.name}"`);
|
|
168
209
|
console.log(` └─ Cluster: ${this._clusterName}`);
|
|
169
210
|
console.log(` └─ Image: ${this._image}`);
|
|
170
211
|
console.log(` └─ CPU: ${this._cpu} | Memory: ${this._memory}MB | Replicas: ${this._replicas}`);
|
|
171
212
|
if (this._port)
|
|
172
213
|
console.log(` └─ Port: ${this._port}`);
|
|
173
214
|
if (Object.keys(this._env).length)
|
|
174
|
-
console.log(` └─ Env vars: ${Object.keys(this._env).join(
|
|
215
|
+
console.log(` └─ Env vars: ${Object.keys(this._env).join(", ")}`);
|
|
175
216
|
this.resolvedArn = `arn:aws:ecs:${region}:000000000000:service/${this._clusterName}/DRYRUN`;
|
|
176
217
|
return { name: this.name, arn: this.resolvedArn };
|
|
177
218
|
}
|
|
@@ -187,16 +228,18 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
187
228
|
image: this._image,
|
|
188
229
|
essential: true,
|
|
189
230
|
logConfiguration: {
|
|
190
|
-
logDriver:
|
|
231
|
+
logDriver: "awslogs",
|
|
191
232
|
options: {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
233
|
+
"awslogs-group": logGroupName,
|
|
234
|
+
"awslogs-region": region,
|
|
235
|
+
"awslogs-stream-prefix": this.name,
|
|
195
236
|
},
|
|
196
237
|
},
|
|
197
238
|
};
|
|
198
239
|
if (this._port)
|
|
199
|
-
containerDef.portMappings = [
|
|
240
|
+
containerDef.portMappings = [
|
|
241
|
+
{ containerPort: this._port, protocol: "tcp" },
|
|
242
|
+
];
|
|
200
243
|
if (Object.keys(this._env).length) {
|
|
201
244
|
const resolvedEnv = await resolveEnvVars(this._env);
|
|
202
245
|
containerDef.environment = Object.entries(resolvedEnv).map(([name, value]) => ({ name, value }));
|
|
@@ -204,7 +247,7 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
204
247
|
const taskDef = await getECSClient().send(new RegisterTaskDefinitionCommand({
|
|
205
248
|
family: this.name,
|
|
206
249
|
networkMode: NetworkMode.AWSVPC,
|
|
207
|
-
requiresCompatibilities: [
|
|
250
|
+
requiresCompatibilities: ["FARGATE"],
|
|
208
251
|
cpu: String(this._cpu),
|
|
209
252
|
memory: String(this._memory),
|
|
210
253
|
executionRoleArn,
|
|
@@ -216,7 +259,7 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
216
259
|
awsvpcConfiguration: {
|
|
217
260
|
subnets: networking.subnetIds,
|
|
218
261
|
securityGroups: networking.securityGroupIds,
|
|
219
|
-
assignPublicIp:
|
|
262
|
+
assignPublicIp: "ENABLED",
|
|
220
263
|
},
|
|
221
264
|
};
|
|
222
265
|
const ecs = getECSClient();
|
|
@@ -245,14 +288,18 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
245
288
|
console.log(`🚀 Created Fargate service "${this.name}" (arn=${this.resolvedArn})`);
|
|
246
289
|
}
|
|
247
290
|
await this.deploySidecars();
|
|
248
|
-
return {
|
|
291
|
+
return {
|
|
292
|
+
name: this.name,
|
|
293
|
+
arn: this.resolvedArn,
|
|
294
|
+
cluster: this._clusterName,
|
|
295
|
+
};
|
|
249
296
|
}
|
|
250
297
|
async destroy() {
|
|
251
298
|
const dryRun = this.isDryRunActive();
|
|
252
299
|
const existing = await this.discoveryPromise;
|
|
253
300
|
console.log(`\n🗑️ Destroying Fargate Service "${this.name}"...`);
|
|
254
301
|
if (!existing) {
|
|
255
|
-
console.log(` ✅ Service "${this.name}" does not exist
|
|
302
|
+
console.log(` ✅ Service "${this.name}" does not exist - nothing to do`);
|
|
256
303
|
return { destroyed: this.name };
|
|
257
304
|
}
|
|
258
305
|
if (dryRun) {
|
|
@@ -260,7 +307,7 @@ export class FargateBuilder extends BaseBuilder {
|
|
|
260
307
|
return { destroyed: this.name };
|
|
261
308
|
}
|
|
262
309
|
const ecs = getECSClient();
|
|
263
|
-
// Drain before delete
|
|
310
|
+
// Drain before delete - required by ECS
|
|
264
311
|
await ecs.send(new UpdateServiceCommand({
|
|
265
312
|
cluster: this._clusterName,
|
|
266
313
|
service: this.name,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { BaseBuilder } from
|
|
2
|
-
import { SecretsBuilder } from
|
|
1
|
+
import { BaseBuilder } from "../../core/resource.js";
|
|
2
|
+
import { SecretsBuilder } from "./secrets.js";
|
|
3
3
|
export declare class LambdaBuilder extends BaseBuilder {
|
|
4
4
|
private _runtime;
|
|
5
5
|
private _handler;
|
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { readFileSync, unlinkSync } from
|
|
2
|
-
import { execSync } from
|
|
3
|
-
import { tmpdir } from
|
|
4
|
-
import { join, extname } from
|
|
5
|
-
import { GetFunctionCommand, CreateFunctionCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, DeleteFunctionCommand, } from
|
|
6
|
-
import { GetRoleCommand, CreateRoleCommand, AttachRolePolicyCommand, } from
|
|
7
|
-
import { BaseBuilder } from
|
|
8
|
-
import { getLambdaClient, getIAMClient } from
|
|
9
|
-
import { resolveEnvVars } from
|
|
1
|
+
import { readFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, extname } from "node:path";
|
|
5
|
+
import { GetFunctionCommand, CreateFunctionCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, DeleteFunctionCommand, } from "@aws-sdk/client-lambda";
|
|
6
|
+
import { GetRoleCommand, CreateRoleCommand, AttachRolePolicyCommand, } from "@aws-sdk/client-iam";
|
|
7
|
+
import { BaseBuilder } from "../../core/resource.js";
|
|
8
|
+
import { getLambdaClient, getIAMClient } from "./api.js";
|
|
9
|
+
import { resolveEnvVars } from "./secrets.js";
|
|
10
10
|
const ASSUME_ROLE_POLICY = JSON.stringify({
|
|
11
|
-
Version:
|
|
12
|
-
Statement: [
|
|
11
|
+
Version: "2012-10-17",
|
|
12
|
+
Statement: [
|
|
13
|
+
{
|
|
14
|
+
Effect: "Allow",
|
|
15
|
+
Principal: { Service: "lambda.amazonaws.com" },
|
|
16
|
+
Action: "sts:AssumeRole",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
13
19
|
});
|
|
14
20
|
export class LambdaBuilder extends BaseBuilder {
|
|
15
|
-
_runtime =
|
|
16
|
-
_handler =
|
|
21
|
+
_runtime = "nodejs20.x";
|
|
22
|
+
_handler = "index.handler";
|
|
17
23
|
_memory = 128;
|
|
18
24
|
_timeout = 30;
|
|
19
25
|
_codePath;
|
|
@@ -31,20 +37,41 @@ export class LambdaBuilder extends BaseBuilder {
|
|
|
31
37
|
return result.Configuration ?? null;
|
|
32
38
|
}
|
|
33
39
|
catch (e) {
|
|
34
|
-
if (e.name ===
|
|
40
|
+
if (e.name === "ResourceNotFoundException")
|
|
35
41
|
return null;
|
|
36
|
-
if (e.name ===
|
|
42
|
+
if (e.name === "CredentialsProviderError")
|
|
37
43
|
return null;
|
|
38
44
|
throw e;
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
|
-
code(pathOrZip) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
code(pathOrZip) {
|
|
48
|
+
this._codePath = pathOrZip;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
runtime(r) {
|
|
52
|
+
this._runtime = r;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
handler(h) {
|
|
56
|
+
this._handler = h;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
memory(mb) {
|
|
60
|
+
this._memory = mb;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
timeout(seconds) {
|
|
64
|
+
this._timeout = seconds;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
role(arn) {
|
|
68
|
+
this._roleArn = arn;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
env(vars) {
|
|
72
|
+
this._env = { ...this._env, ...vars };
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
48
75
|
async ensureRole() {
|
|
49
76
|
if (this._roleArn)
|
|
50
77
|
return this._roleArn;
|
|
@@ -55,7 +82,7 @@ export class LambdaBuilder extends BaseBuilder {
|
|
|
55
82
|
return existing.Role.Arn;
|
|
56
83
|
}
|
|
57
84
|
catch (e) {
|
|
58
|
-
if (e.name !==
|
|
85
|
+
if (e.name !== "NoSuchEntityException")
|
|
59
86
|
throw e;
|
|
60
87
|
}
|
|
61
88
|
const created = await iam.send(new CreateRoleCommand({
|
|
@@ -65,21 +92,23 @@ export class LambdaBuilder extends BaseBuilder {
|
|
|
65
92
|
}));
|
|
66
93
|
await iam.send(new AttachRolePolicyCommand({
|
|
67
94
|
RoleName: roleName,
|
|
68
|
-
PolicyArn:
|
|
95
|
+
PolicyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
|
|
69
96
|
}));
|
|
70
97
|
console.log(` ✅ Created execution role: ${roleName}`);
|
|
71
|
-
// IAM propagation
|
|
72
|
-
await new Promise(r => setTimeout(r, 10_000));
|
|
98
|
+
// IAM propagation - Lambda rejects a brand-new role for ~10s
|
|
99
|
+
await new Promise((r) => setTimeout(r, 10_000));
|
|
73
100
|
return created.Role.Arn;
|
|
74
101
|
}
|
|
75
102
|
buildZip() {
|
|
76
103
|
if (!this._codePath)
|
|
77
104
|
throw new Error(`[Lambda:${this.name}] .code(path) is required`);
|
|
78
|
-
if (extname(this._codePath) ===
|
|
105
|
+
if (extname(this._codePath) === ".zip") {
|
|
79
106
|
return readFileSync(this._codePath);
|
|
80
107
|
}
|
|
81
108
|
const outPath = join(tmpdir(), `puls-lambda-${this.name}-${Date.now()}.zip`);
|
|
82
|
-
execSync(`cd "${this._codePath}" && zip -r "${outPath}" .`, {
|
|
109
|
+
execSync(`cd "${this._codePath}" && zip -r "${outPath}" .`, {
|
|
110
|
+
stdio: "pipe",
|
|
111
|
+
});
|
|
83
112
|
const buf = readFileSync(outPath);
|
|
84
113
|
unlinkSync(outPath);
|
|
85
114
|
return buf;
|
|
@@ -90,13 +119,13 @@ export class LambdaBuilder extends BaseBuilder {
|
|
|
90
119
|
const lambda = getLambdaClient();
|
|
91
120
|
console.log(`\n⚡ Finalizing Lambda Function "${this.name}"...`);
|
|
92
121
|
if (dryRun) {
|
|
93
|
-
console.log(` 📝 [PLAN] ${existing ?
|
|
122
|
+
console.log(` 📝 [PLAN] ${existing ? "Update" : "Create"} function "${this.name}"`);
|
|
94
123
|
console.log(` └─ Runtime: ${this._runtime} | Handler: ${this._handler}`);
|
|
95
124
|
console.log(` └─ Memory: ${this._memory}MB | Timeout: ${this._timeout}s`);
|
|
96
125
|
if (this._codePath)
|
|
97
126
|
console.log(` └─ Code: ${this._codePath}`);
|
|
98
127
|
if (Object.keys(this._env).length)
|
|
99
|
-
console.log(` └─ Env vars: ${Object.keys(this._env).join(
|
|
128
|
+
console.log(` └─ Env vars: ${Object.keys(this._env).join(", ")}`);
|
|
100
129
|
this.resolvedArn = `arn:aws:lambda:DRYRUN:000000000000:function:${this.name}`;
|
|
101
130
|
return { name: this.name, arn: this.resolvedArn };
|
|
102
131
|
}
|
|
@@ -111,7 +140,9 @@ export class LambdaBuilder extends BaseBuilder {
|
|
|
111
140
|
MemorySize: this._memory,
|
|
112
141
|
Timeout: this._timeout,
|
|
113
142
|
Role: roleArn,
|
|
114
|
-
Environment: Object.keys(resolvedEnv).length
|
|
143
|
+
Environment: Object.keys(resolvedEnv).length
|
|
144
|
+
? { Variables: resolvedEnv }
|
|
145
|
+
: undefined,
|
|
115
146
|
};
|
|
116
147
|
if (existing) {
|
|
117
148
|
this.resolvedArn = existing.FunctionArn;
|
|
@@ -145,7 +176,7 @@ export class LambdaBuilder extends BaseBuilder {
|
|
|
145
176
|
const existing = await this.discoveryPromise;
|
|
146
177
|
console.log(`\n🗑️ Destroying Lambda Function "${this.name}"...`);
|
|
147
178
|
if (!existing) {
|
|
148
|
-
console.log(` ✅ Function "${this.name}" does not exist
|
|
179
|
+
console.log(` ✅ Function "${this.name}" does not exist - nothing to do`);
|
|
149
180
|
return { destroyed: this.name };
|
|
150
181
|
}
|
|
151
182
|
if (dryRun) {
|