@stacksjs/ts-cloud 0.1.8 → 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/dist/bin/cli.js +1 -1
- package/package.json +18 -16
- package/src/aws/acm.ts +768 -0
- package/src/aws/application-autoscaling.ts +845 -0
- package/src/aws/bedrock.ts +4074 -0
- package/src/aws/client.ts +891 -0
- package/src/aws/cloudformation.ts +896 -0
- package/src/aws/cloudfront.ts +1531 -0
- package/src/aws/cloudwatch-logs.ts +154 -0
- package/src/aws/comprehend.ts +839 -0
- package/src/aws/connect.ts +1056 -0
- package/src/aws/deploy-imap.ts +384 -0
- package/src/aws/dynamodb.ts +340 -0
- package/src/aws/ec2.ts +1385 -0
- package/src/aws/ecr.ts +621 -0
- package/src/aws/ecs.ts +615 -0
- package/src/aws/elasticache.ts +301 -0
- package/src/aws/elbv2.ts +942 -0
- package/src/aws/email.ts +928 -0
- package/src/aws/eventbridge.ts +248 -0
- package/src/aws/iam.ts +1689 -0
- package/src/aws/imap-server.ts +2100 -0
- package/src/aws/index.ts +213 -0
- package/src/aws/kendra.ts +1097 -0
- package/src/aws/lambda.ts +786 -0
- package/src/aws/opensearch.ts +158 -0
- package/src/aws/personalize.ts +977 -0
- package/src/aws/polly.ts +559 -0
- package/src/aws/rds.ts +888 -0
- package/src/aws/rekognition.ts +846 -0
- package/src/aws/route53-domains.ts +359 -0
- package/src/aws/route53.ts +1046 -0
- package/src/aws/s3.ts +2334 -0
- package/src/aws/scheduler.ts +571 -0
- package/src/aws/secrets-manager.ts +769 -0
- package/src/aws/ses.ts +1081 -0
- package/src/aws/setup-phone.ts +104 -0
- package/src/aws/setup-sms.ts +580 -0
- package/src/aws/sms.ts +1735 -0
- package/src/aws/smtp-server.ts +531 -0
- package/src/aws/sns.ts +758 -0
- package/src/aws/sqs.ts +382 -0
- package/src/aws/ssm.ts +807 -0
- package/src/aws/sts.ts +92 -0
- package/src/aws/support.ts +391 -0
- package/src/aws/test-imap.ts +86 -0
- package/src/aws/textract.ts +780 -0
- package/src/aws/transcribe.ts +108 -0
- package/src/aws/translate.ts +641 -0
- package/src/aws/voice.ts +1379 -0
- package/src/config.ts +35 -0
- package/src/deploy/index.ts +7 -0
- package/src/deploy/static-site-external-dns.ts +945 -0
- package/src/deploy/static-site.ts +1175 -0
- package/src/dns/cloudflare.ts +548 -0
- package/src/dns/godaddy.ts +412 -0
- package/src/dns/index.ts +205 -0
- package/src/dns/porkbun.ts +362 -0
- package/src/dns/route53-adapter.ts +414 -0
- package/src/dns/types.ts +119 -0
- package/src/dns/validator.ts +369 -0
- package/src/generators/index.ts +5 -0
- package/src/generators/infrastructure.ts +1660 -0
- package/src/index.ts +163 -0
- package/src/push/apns.ts +452 -0
- package/src/push/fcm.ts +506 -0
- package/src/push/index.ts +58 -0
- package/src/security/pre-deploy-scanner.ts +655 -0
- package/src/ssl/acme-client.ts +478 -0
- package/src/ssl/index.ts +7 -0
- package/src/ssl/letsencrypt.ts +747 -0
- package/src/types.ts +2 -0
- package/src/utils/cli.ts +398 -0
- package/src/validation/index.ts +5 -0
- package/src/validation/template.ts +405 -0
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS Lambda Operations
|
|
3
|
+
* Direct API calls without AWS SDK dependency
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AWSClient } from './client'
|
|
7
|
+
import { deflateRawSync } from 'zlib'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a minimal ZIP file containing a single file
|
|
11
|
+
* This is a pure implementation without external dependencies
|
|
12
|
+
*/
|
|
13
|
+
function createZipFile(filename: string, content: string | Buffer): Buffer {
|
|
14
|
+
const data = typeof content === 'string' ? Buffer.from(content, 'utf-8') : content
|
|
15
|
+
const compressedData = deflateRawSync(data)
|
|
16
|
+
|
|
17
|
+
// Calculate CRC32
|
|
18
|
+
const crc32 = calculateCrc32(data)
|
|
19
|
+
|
|
20
|
+
// Current date/time in DOS format
|
|
21
|
+
const now = new Date()
|
|
22
|
+
const dosTime = ((now.getHours() << 11) | (now.getMinutes() << 5) | (now.getSeconds() >> 1)) & 0xffff
|
|
23
|
+
const dosDate = (((now.getFullYear() - 1980) << 9) | ((now.getMonth() + 1) << 5) | now.getDate()) & 0xffff
|
|
24
|
+
|
|
25
|
+
const filenameBuffer = Buffer.from(filename, 'utf-8')
|
|
26
|
+
|
|
27
|
+
// Local file header
|
|
28
|
+
const localHeader = Buffer.alloc(30 + filenameBuffer.length)
|
|
29
|
+
localHeader.writeUInt32LE(0x04034b50, 0) // Local file header signature
|
|
30
|
+
localHeader.writeUInt16LE(20, 4) // Version needed to extract
|
|
31
|
+
localHeader.writeUInt16LE(0, 6) // General purpose bit flag
|
|
32
|
+
localHeader.writeUInt16LE(8, 8) // Compression method (deflate)
|
|
33
|
+
localHeader.writeUInt16LE(dosTime, 10) // Last mod file time
|
|
34
|
+
localHeader.writeUInt16LE(dosDate, 12) // Last mod file date
|
|
35
|
+
localHeader.writeUInt32LE(crc32, 14) // CRC-32
|
|
36
|
+
localHeader.writeUInt32LE(compressedData.length, 18) // Compressed size
|
|
37
|
+
localHeader.writeUInt32LE(data.length, 22) // Uncompressed size
|
|
38
|
+
localHeader.writeUInt16LE(filenameBuffer.length, 26) // File name length
|
|
39
|
+
localHeader.writeUInt16LE(0, 28) // Extra field length
|
|
40
|
+
filenameBuffer.copy(localHeader, 30)
|
|
41
|
+
|
|
42
|
+
// Central directory header
|
|
43
|
+
const centralHeader = Buffer.alloc(46 + filenameBuffer.length)
|
|
44
|
+
centralHeader.writeUInt32LE(0x02014b50, 0) // Central directory signature
|
|
45
|
+
centralHeader.writeUInt16LE(20, 4) // Version made by
|
|
46
|
+
centralHeader.writeUInt16LE(20, 6) // Version needed to extract
|
|
47
|
+
centralHeader.writeUInt16LE(0, 8) // General purpose bit flag
|
|
48
|
+
centralHeader.writeUInt16LE(8, 10) // Compression method
|
|
49
|
+
centralHeader.writeUInt16LE(dosTime, 12) // Last mod file time
|
|
50
|
+
centralHeader.writeUInt16LE(dosDate, 14) // Last mod file date
|
|
51
|
+
centralHeader.writeUInt32LE(crc32, 16) // CRC-32
|
|
52
|
+
centralHeader.writeUInt32LE(compressedData.length, 20) // Compressed size
|
|
53
|
+
centralHeader.writeUInt32LE(data.length, 24) // Uncompressed size
|
|
54
|
+
centralHeader.writeUInt16LE(filenameBuffer.length, 28) // File name length
|
|
55
|
+
centralHeader.writeUInt16LE(0, 30) // Extra field length
|
|
56
|
+
centralHeader.writeUInt16LE(0, 32) // File comment length
|
|
57
|
+
centralHeader.writeUInt16LE(0, 34) // Disk number start
|
|
58
|
+
centralHeader.writeUInt16LE(0, 36) // Internal file attributes
|
|
59
|
+
centralHeader.writeUInt32LE(0, 38) // External file attributes
|
|
60
|
+
centralHeader.writeUInt32LE(0, 42) // Relative offset of local header
|
|
61
|
+
filenameBuffer.copy(centralHeader, 46)
|
|
62
|
+
|
|
63
|
+
// End of central directory record
|
|
64
|
+
const centralDirOffset = localHeader.length + compressedData.length
|
|
65
|
+
const centralDirSize = centralHeader.length
|
|
66
|
+
const endRecord = Buffer.alloc(22)
|
|
67
|
+
endRecord.writeUInt32LE(0x06054b50, 0) // End of central directory signature
|
|
68
|
+
endRecord.writeUInt16LE(0, 4) // Number of this disk
|
|
69
|
+
endRecord.writeUInt16LE(0, 6) // Disk where central directory starts
|
|
70
|
+
endRecord.writeUInt16LE(1, 8) // Number of central directory records on this disk
|
|
71
|
+
endRecord.writeUInt16LE(1, 10) // Total number of central directory records
|
|
72
|
+
endRecord.writeUInt32LE(centralDirSize, 12) // Size of central directory
|
|
73
|
+
endRecord.writeUInt32LE(centralDirOffset, 16) // Offset of start of central directory
|
|
74
|
+
endRecord.writeUInt16LE(0, 20) // Comment length
|
|
75
|
+
|
|
76
|
+
return Buffer.concat([localHeader, compressedData, centralHeader, endRecord])
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Calculate CRC32 checksum
|
|
81
|
+
*/
|
|
82
|
+
function calculateCrc32(data: Buffer): number {
|
|
83
|
+
// CRC32 lookup table
|
|
84
|
+
const table: number[] = []
|
|
85
|
+
for (let i = 0; i < 256; i++) {
|
|
86
|
+
let c = i
|
|
87
|
+
for (let j = 0; j < 8; j++) {
|
|
88
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
|
|
89
|
+
}
|
|
90
|
+
table[i] = c
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let crc = 0xffffffff
|
|
94
|
+
for (let i = 0; i < data.length; i++) {
|
|
95
|
+
crc = table[(crc ^ data[i]) & 0xff] ^ (crc >>> 8)
|
|
96
|
+
}
|
|
97
|
+
return (crc ^ 0xffffffff) >>> 0
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface LambdaFunctionConfiguration {
|
|
101
|
+
FunctionName?: string
|
|
102
|
+
FunctionArn?: string
|
|
103
|
+
Runtime?: string
|
|
104
|
+
Role?: string
|
|
105
|
+
Handler?: string
|
|
106
|
+
CodeSize?: number
|
|
107
|
+
Description?: string
|
|
108
|
+
Timeout?: number
|
|
109
|
+
MemorySize?: number
|
|
110
|
+
LastModified?: string
|
|
111
|
+
CodeSha256?: string
|
|
112
|
+
Version?: string
|
|
113
|
+
State?: 'Pending' | 'Active' | 'Inactive' | 'Failed'
|
|
114
|
+
StateReason?: string
|
|
115
|
+
StateReasonCode?: string
|
|
116
|
+
Environment?: {
|
|
117
|
+
Variables?: Record<string, string>
|
|
118
|
+
}
|
|
119
|
+
Architectures?: ('x86_64' | 'arm64')[]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface InvokeResult {
|
|
123
|
+
StatusCode?: number
|
|
124
|
+
FunctionError?: string
|
|
125
|
+
LogResult?: string
|
|
126
|
+
Payload?: string
|
|
127
|
+
ExecutedVersion?: string
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface CreateFunctionParams {
|
|
131
|
+
FunctionName: string
|
|
132
|
+
Runtime: 'nodejs18.x' | 'nodejs20.x' | 'python3.11' | 'python3.12' | string
|
|
133
|
+
Role: string
|
|
134
|
+
Handler: string
|
|
135
|
+
Code: {
|
|
136
|
+
ZipFile?: string // Base64 encoded
|
|
137
|
+
S3Bucket?: string
|
|
138
|
+
S3Key?: string
|
|
139
|
+
S3ObjectVersion?: string
|
|
140
|
+
}
|
|
141
|
+
Description?: string
|
|
142
|
+
Timeout?: number
|
|
143
|
+
MemorySize?: number
|
|
144
|
+
Environment?: {
|
|
145
|
+
Variables: Record<string, string>
|
|
146
|
+
}
|
|
147
|
+
Tags?: Record<string, string>
|
|
148
|
+
Architectures?: ('x86_64' | 'arm64')[]
|
|
149
|
+
EphemeralStorage?: {
|
|
150
|
+
Size: number
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface UpdateFunctionCodeParams {
|
|
155
|
+
FunctionName: string
|
|
156
|
+
ZipFile?: string // Base64 encoded
|
|
157
|
+
S3Bucket?: string
|
|
158
|
+
S3Key?: string
|
|
159
|
+
S3ObjectVersion?: string
|
|
160
|
+
Publish?: boolean
|
|
161
|
+
Architectures?: ('x86_64' | 'arm64')[]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface AddPermissionParams {
|
|
165
|
+
FunctionName: string
|
|
166
|
+
StatementId: string
|
|
167
|
+
Action: string
|
|
168
|
+
Principal: string
|
|
169
|
+
SourceArn?: string
|
|
170
|
+
SourceAccount?: string
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Lambda service management using direct API calls
|
|
175
|
+
*/
|
|
176
|
+
export class LambdaClient {
|
|
177
|
+
private client: AWSClient
|
|
178
|
+
private region: string
|
|
179
|
+
|
|
180
|
+
constructor(region: string = 'us-east-1') {
|
|
181
|
+
this.region = region
|
|
182
|
+
this.client = new AWSClient()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a new Lambda function
|
|
187
|
+
*/
|
|
188
|
+
async createFunction(params: CreateFunctionParams): Promise<LambdaFunctionConfiguration> {
|
|
189
|
+
const result = await this.client.request({
|
|
190
|
+
service: 'lambda',
|
|
191
|
+
region: this.region,
|
|
192
|
+
method: 'POST',
|
|
193
|
+
path: '/2015-03-31/functions',
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
},
|
|
197
|
+
body: JSON.stringify(params),
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return result
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get function configuration
|
|
205
|
+
*/
|
|
206
|
+
async getFunction(functionName: string): Promise<{
|
|
207
|
+
Configuration?: LambdaFunctionConfiguration
|
|
208
|
+
Code?: {
|
|
209
|
+
RepositoryType?: string
|
|
210
|
+
Location?: string
|
|
211
|
+
}
|
|
212
|
+
Tags?: Record<string, string>
|
|
213
|
+
}> {
|
|
214
|
+
const result = await this.client.request({
|
|
215
|
+
service: 'lambda',
|
|
216
|
+
region: this.region,
|
|
217
|
+
method: 'GET',
|
|
218
|
+
path: `/2015-03-31/functions/${encodeURIComponent(functionName)}`,
|
|
219
|
+
headers: {
|
|
220
|
+
'Content-Type': 'application/json',
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
return result
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Update function code
|
|
229
|
+
*/
|
|
230
|
+
async updateFunctionCode(params: UpdateFunctionCodeParams): Promise<LambdaFunctionConfiguration> {
|
|
231
|
+
const { FunctionName, ...rest } = params
|
|
232
|
+
const result = await this.client.request({
|
|
233
|
+
service: 'lambda',
|
|
234
|
+
region: this.region,
|
|
235
|
+
method: 'PUT',
|
|
236
|
+
path: `/2015-03-31/functions/${encodeURIComponent(FunctionName)}/code`,
|
|
237
|
+
headers: {
|
|
238
|
+
'Content-Type': 'application/json',
|
|
239
|
+
},
|
|
240
|
+
body: JSON.stringify(rest),
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
return result
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Update function code with inline JavaScript/TypeScript code
|
|
248
|
+
* Automatically creates a zip file with the code as index.js
|
|
249
|
+
*/
|
|
250
|
+
async updateFunctionCodeInline(functionName: string, code: string, filename: string = 'index.js'): Promise<LambdaFunctionConfiguration> {
|
|
251
|
+
const zipBuffer = createZipFile(filename, code)
|
|
252
|
+
return this.updateFunctionCode({
|
|
253
|
+
FunctionName: functionName,
|
|
254
|
+
ZipFile: zipBuffer.toString('base64'),
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Update function configuration
|
|
260
|
+
*/
|
|
261
|
+
async updateFunctionConfiguration(params: {
|
|
262
|
+
FunctionName: string
|
|
263
|
+
Runtime?: string
|
|
264
|
+
Role?: string
|
|
265
|
+
Handler?: string
|
|
266
|
+
Description?: string
|
|
267
|
+
Timeout?: number
|
|
268
|
+
MemorySize?: number
|
|
269
|
+
Environment?: { Variables: Record<string, string> }
|
|
270
|
+
}): Promise<LambdaFunctionConfiguration> {
|
|
271
|
+
const { FunctionName, ...rest } = params
|
|
272
|
+
const result = await this.client.request({
|
|
273
|
+
service: 'lambda',
|
|
274
|
+
region: this.region,
|
|
275
|
+
method: 'PUT',
|
|
276
|
+
path: `/2015-03-31/functions/${encodeURIComponent(FunctionName)}/configuration`,
|
|
277
|
+
headers: {
|
|
278
|
+
'Content-Type': 'application/json',
|
|
279
|
+
},
|
|
280
|
+
body: JSON.stringify(rest),
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return result
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Delete a Lambda function
|
|
288
|
+
*/
|
|
289
|
+
async deleteFunction(functionName: string): Promise<void> {
|
|
290
|
+
await this.client.request({
|
|
291
|
+
service: 'lambda',
|
|
292
|
+
region: this.region,
|
|
293
|
+
method: 'DELETE',
|
|
294
|
+
path: `/2015-03-31/functions/${encodeURIComponent(functionName)}`,
|
|
295
|
+
headers: {
|
|
296
|
+
'Content-Type': 'application/json',
|
|
297
|
+
},
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Invoke a Lambda function
|
|
303
|
+
*/
|
|
304
|
+
async invoke(params: {
|
|
305
|
+
FunctionName: string
|
|
306
|
+
InvocationType?: 'RequestResponse' | 'Event' | 'DryRun'
|
|
307
|
+
Payload?: string | object
|
|
308
|
+
LogType?: 'None' | 'Tail'
|
|
309
|
+
}): Promise<InvokeResult> {
|
|
310
|
+
const { FunctionName, InvocationType = 'RequestResponse', Payload, LogType } = params
|
|
311
|
+
|
|
312
|
+
const headers: Record<string, string> = {
|
|
313
|
+
'Content-Type': 'application/json',
|
|
314
|
+
'X-Amz-Invocation-Type': InvocationType,
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (LogType) {
|
|
318
|
+
headers['X-Amz-Log-Type'] = LogType
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const body = Payload
|
|
322
|
+
? typeof Payload === 'string'
|
|
323
|
+
? Payload
|
|
324
|
+
: JSON.stringify(Payload)
|
|
325
|
+
: undefined
|
|
326
|
+
|
|
327
|
+
const result = await this.client.request({
|
|
328
|
+
service: 'lambda',
|
|
329
|
+
region: this.region,
|
|
330
|
+
method: 'POST',
|
|
331
|
+
path: `/2015-03-31/functions/${encodeURIComponent(FunctionName)}/invocations`,
|
|
332
|
+
headers,
|
|
333
|
+
body,
|
|
334
|
+
returnHeaders: true,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
StatusCode: result.statusCode || 200,
|
|
339
|
+
FunctionError: result.headers?.['x-amz-function-error'],
|
|
340
|
+
LogResult: result.headers?.['x-amz-log-result'],
|
|
341
|
+
Payload: typeof result.body === 'string' ? result.body : JSON.stringify(result.body),
|
|
342
|
+
ExecutedVersion: result.headers?.['x-amz-executed-version'],
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* List Lambda functions
|
|
348
|
+
*/
|
|
349
|
+
async listFunctions(params?: {
|
|
350
|
+
MaxItems?: number
|
|
351
|
+
Marker?: string
|
|
352
|
+
FunctionVersion?: 'ALL'
|
|
353
|
+
}): Promise<{
|
|
354
|
+
Functions?: LambdaFunctionConfiguration[]
|
|
355
|
+
NextMarker?: string
|
|
356
|
+
}> {
|
|
357
|
+
const queryParams: Record<string, string> = {}
|
|
358
|
+
if (params?.MaxItems) queryParams.MaxItems = String(params.MaxItems)
|
|
359
|
+
if (params?.Marker) queryParams.Marker = params.Marker
|
|
360
|
+
if (params?.FunctionVersion) queryParams.FunctionVersion = params.FunctionVersion
|
|
361
|
+
|
|
362
|
+
const result = await this.client.request({
|
|
363
|
+
service: 'lambda',
|
|
364
|
+
region: this.region,
|
|
365
|
+
method: 'GET',
|
|
366
|
+
path: '/2015-03-31/functions',
|
|
367
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
368
|
+
headers: {
|
|
369
|
+
'Content-Type': 'application/json',
|
|
370
|
+
},
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
return result
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Add permission to Lambda function (resource-based policy)
|
|
378
|
+
*/
|
|
379
|
+
async addPermission(params: AddPermissionParams): Promise<{ Statement?: string }> {
|
|
380
|
+
const { FunctionName, ...rest } = params
|
|
381
|
+
const result = await this.client.request({
|
|
382
|
+
service: 'lambda',
|
|
383
|
+
region: this.region,
|
|
384
|
+
method: 'POST',
|
|
385
|
+
path: `/2015-03-31/functions/${encodeURIComponent(FunctionName)}/policy`,
|
|
386
|
+
headers: {
|
|
387
|
+
'Content-Type': 'application/json',
|
|
388
|
+
},
|
|
389
|
+
body: JSON.stringify(rest),
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
return result
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Remove permission from Lambda function
|
|
397
|
+
*/
|
|
398
|
+
async removePermission(functionName: string, statementId: string): Promise<void> {
|
|
399
|
+
await this.client.request({
|
|
400
|
+
service: 'lambda',
|
|
401
|
+
region: this.region,
|
|
402
|
+
method: 'DELETE',
|
|
403
|
+
path: `/2015-03-31/functions/${encodeURIComponent(functionName)}/policy/${encodeURIComponent(statementId)}`,
|
|
404
|
+
headers: {
|
|
405
|
+
'Content-Type': 'application/json',
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Publish a version of the function
|
|
412
|
+
*/
|
|
413
|
+
async publishVersion(params: {
|
|
414
|
+
FunctionName: string
|
|
415
|
+
Description?: string
|
|
416
|
+
CodeSha256?: string
|
|
417
|
+
}): Promise<LambdaFunctionConfiguration> {
|
|
418
|
+
const { FunctionName, ...rest } = params
|
|
419
|
+
const result = await this.client.request({
|
|
420
|
+
service: 'lambda',
|
|
421
|
+
region: this.region,
|
|
422
|
+
method: 'POST',
|
|
423
|
+
path: `/2015-03-31/functions/${encodeURIComponent(FunctionName)}/versions`,
|
|
424
|
+
headers: {
|
|
425
|
+
'Content-Type': 'application/json',
|
|
426
|
+
},
|
|
427
|
+
body: JSON.stringify(rest),
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
return result
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Create an alias for a function version
|
|
435
|
+
*/
|
|
436
|
+
async createAlias(params: {
|
|
437
|
+
FunctionName: string
|
|
438
|
+
Name: string
|
|
439
|
+
FunctionVersion: string
|
|
440
|
+
Description?: string
|
|
441
|
+
}): Promise<{
|
|
442
|
+
AliasArn?: string
|
|
443
|
+
Name?: string
|
|
444
|
+
FunctionVersion?: string
|
|
445
|
+
Description?: string
|
|
446
|
+
}> {
|
|
447
|
+
const { FunctionName, ...rest } = params
|
|
448
|
+
const result = await this.client.request({
|
|
449
|
+
service: 'lambda',
|
|
450
|
+
region: this.region,
|
|
451
|
+
method: 'POST',
|
|
452
|
+
path: `/2015-03-31/functions/${encodeURIComponent(FunctionName)}/aliases`,
|
|
453
|
+
headers: {
|
|
454
|
+
'Content-Type': 'application/json',
|
|
455
|
+
},
|
|
456
|
+
body: JSON.stringify(rest),
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
return result
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Wait for function to become active
|
|
464
|
+
*/
|
|
465
|
+
async waitForFunctionActive(functionName: string, maxWaitSeconds: number = 60): Promise<LambdaFunctionConfiguration> {
|
|
466
|
+
const startTime = Date.now()
|
|
467
|
+
const maxWaitMs = maxWaitSeconds * 1000
|
|
468
|
+
|
|
469
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
470
|
+
try {
|
|
471
|
+
const response = await this.getFunction(functionName)
|
|
472
|
+
const state = response.Configuration?.State
|
|
473
|
+
|
|
474
|
+
if (state === 'Active') {
|
|
475
|
+
return response.Configuration!
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (state === 'Failed') {
|
|
479
|
+
throw new Error(`Function ${functionName} failed: ${response.Configuration?.StateReason}`)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Wait 2 seconds before checking again
|
|
483
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
484
|
+
}
|
|
485
|
+
catch (error: any) {
|
|
486
|
+
if (error.code === 'ResourceNotFoundException') {
|
|
487
|
+
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
488
|
+
continue
|
|
489
|
+
}
|
|
490
|
+
throw error
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
throw new Error(`Timeout waiting for function ${functionName} to become active`)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Check if function exists
|
|
499
|
+
*/
|
|
500
|
+
async functionExists(functionName: string): Promise<boolean> {
|
|
501
|
+
try {
|
|
502
|
+
await this.getFunction(functionName)
|
|
503
|
+
return true
|
|
504
|
+
}
|
|
505
|
+
catch (error: any) {
|
|
506
|
+
if (error.code === 'ResourceNotFoundException' || error.statusCode === 404) {
|
|
507
|
+
return false
|
|
508
|
+
}
|
|
509
|
+
throw error
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Create a function URL for the Lambda function
|
|
515
|
+
*/
|
|
516
|
+
async createFunctionUrl(params: {
|
|
517
|
+
FunctionName: string
|
|
518
|
+
AuthType: 'NONE' | 'AWS_IAM'
|
|
519
|
+
Cors?: {
|
|
520
|
+
AllowOrigins?: string[]
|
|
521
|
+
AllowMethods?: string[]
|
|
522
|
+
AllowHeaders?: string[]
|
|
523
|
+
ExposeHeaders?: string[]
|
|
524
|
+
MaxAge?: number
|
|
525
|
+
AllowCredentials?: boolean
|
|
526
|
+
}
|
|
527
|
+
InvokeMode?: 'BUFFERED' | 'RESPONSE_STREAM'
|
|
528
|
+
}): Promise<{
|
|
529
|
+
FunctionUrl?: string
|
|
530
|
+
FunctionArn?: string
|
|
531
|
+
AuthType?: string
|
|
532
|
+
CreationTime?: string
|
|
533
|
+
}> {
|
|
534
|
+
const { FunctionName, ...rest } = params
|
|
535
|
+
const result = await this.client.request({
|
|
536
|
+
service: 'lambda',
|
|
537
|
+
region: this.region,
|
|
538
|
+
method: 'POST',
|
|
539
|
+
path: `/2021-10-31/functions/${encodeURIComponent(FunctionName)}/url`,
|
|
540
|
+
headers: {
|
|
541
|
+
'Content-Type': 'application/json',
|
|
542
|
+
},
|
|
543
|
+
body: JSON.stringify(rest),
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
return result
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Get function URL configuration
|
|
551
|
+
*/
|
|
552
|
+
async getFunctionUrl(functionName: string): Promise<{
|
|
553
|
+
FunctionUrl?: string
|
|
554
|
+
FunctionArn?: string
|
|
555
|
+
AuthType?: string
|
|
556
|
+
Cors?: {
|
|
557
|
+
AllowOrigins?: string[]
|
|
558
|
+
AllowMethods?: string[]
|
|
559
|
+
AllowHeaders?: string[]
|
|
560
|
+
}
|
|
561
|
+
CreationTime?: string
|
|
562
|
+
} | null> {
|
|
563
|
+
try {
|
|
564
|
+
const result = await this.client.request({
|
|
565
|
+
service: 'lambda',
|
|
566
|
+
region: this.region,
|
|
567
|
+
method: 'GET',
|
|
568
|
+
path: `/2021-10-31/functions/${encodeURIComponent(functionName)}/url`,
|
|
569
|
+
headers: {
|
|
570
|
+
'Content-Type': 'application/json',
|
|
571
|
+
},
|
|
572
|
+
})
|
|
573
|
+
return result
|
|
574
|
+
}
|
|
575
|
+
catch (error: any) {
|
|
576
|
+
if (error.statusCode === 404) {
|
|
577
|
+
return null
|
|
578
|
+
}
|
|
579
|
+
throw error
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Delete function URL configuration
|
|
585
|
+
*/
|
|
586
|
+
async deleteFunctionUrl(functionName: string): Promise<void> {
|
|
587
|
+
await this.client.request({
|
|
588
|
+
service: 'lambda',
|
|
589
|
+
region: this.region,
|
|
590
|
+
method: 'DELETE',
|
|
591
|
+
path: `/2021-10-31/functions/${encodeURIComponent(functionName)}/url`,
|
|
592
|
+
headers: {
|
|
593
|
+
'Content-Type': 'application/json',
|
|
594
|
+
},
|
|
595
|
+
})
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Create function with inline code (convenience method)
|
|
600
|
+
*/
|
|
601
|
+
async createFunctionWithCode(params: {
|
|
602
|
+
FunctionName: string
|
|
603
|
+
Runtime: 'nodejs18.x' | 'nodejs20.x' | 'python3.11' | 'python3.12' | string
|
|
604
|
+
Role: string
|
|
605
|
+
Handler: string
|
|
606
|
+
Code: string
|
|
607
|
+
Filename?: string
|
|
608
|
+
Description?: string
|
|
609
|
+
Timeout?: number
|
|
610
|
+
MemorySize?: number
|
|
611
|
+
Environment?: { Variables: Record<string, string> }
|
|
612
|
+
}): Promise<LambdaFunctionConfiguration> {
|
|
613
|
+
const { Code, Filename = 'index.js', ...rest } = params
|
|
614
|
+
const zipBuffer = createZipFile(Filename, Code)
|
|
615
|
+
|
|
616
|
+
return this.createFunction({
|
|
617
|
+
...rest,
|
|
618
|
+
Code: {
|
|
619
|
+
ZipFile: zipBuffer.toString('base64'),
|
|
620
|
+
},
|
|
621
|
+
})
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Add permission for Function URL public access
|
|
626
|
+
*/
|
|
627
|
+
async addFunctionUrlPermission(functionName: string): Promise<{ Statement?: string }> {
|
|
628
|
+
return this.addPermission({
|
|
629
|
+
FunctionName: functionName,
|
|
630
|
+
StatementId: 'FunctionURLAllowPublicAccess',
|
|
631
|
+
Action: 'lambda:InvokeFunctionUrl',
|
|
632
|
+
Principal: '*',
|
|
633
|
+
// @ts-ignore - FunctionUrlAuthType is a valid parameter
|
|
634
|
+
FunctionUrlAuthType: 'NONE',
|
|
635
|
+
})
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// =========================================================================
|
|
639
|
+
// Layer Operations
|
|
640
|
+
// =========================================================================
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Publish a new Lambda layer version
|
|
644
|
+
*/
|
|
645
|
+
async publishLayerVersion(params: {
|
|
646
|
+
LayerName: string
|
|
647
|
+
Description?: string
|
|
648
|
+
Content: {
|
|
649
|
+
S3Bucket?: string
|
|
650
|
+
S3Key?: string
|
|
651
|
+
ZipFile?: string // Base64 encoded zip
|
|
652
|
+
}
|
|
653
|
+
CompatibleRuntimes?: string[]
|
|
654
|
+
CompatibleArchitectures?: ('x86_64' | 'arm64')[]
|
|
655
|
+
LicenseInfo?: string
|
|
656
|
+
}): Promise<{
|
|
657
|
+
LayerArn?: string
|
|
658
|
+
LayerVersionArn?: string
|
|
659
|
+
Description?: string
|
|
660
|
+
Version?: number
|
|
661
|
+
CompatibleRuntimes?: string[]
|
|
662
|
+
CompatibleArchitectures?: string[]
|
|
663
|
+
}> {
|
|
664
|
+
const { LayerName, ...rest } = params
|
|
665
|
+
const result = await this.client.request({
|
|
666
|
+
service: 'lambda',
|
|
667
|
+
region: this.region,
|
|
668
|
+
method: 'POST',
|
|
669
|
+
path: `/2018-10-31/layers/${encodeURIComponent(LayerName)}/versions`,
|
|
670
|
+
headers: {
|
|
671
|
+
'Content-Type': 'application/json',
|
|
672
|
+
},
|
|
673
|
+
body: JSON.stringify(rest),
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
return result
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* List layer versions
|
|
681
|
+
*/
|
|
682
|
+
async listLayerVersions(layerName: string, params?: {
|
|
683
|
+
CompatibleRuntime?: string
|
|
684
|
+
CompatibleArchitecture?: 'x86_64' | 'arm64'
|
|
685
|
+
MaxItems?: number
|
|
686
|
+
Marker?: string
|
|
687
|
+
}): Promise<{
|
|
688
|
+
LayerVersions?: Array<{
|
|
689
|
+
LayerVersionArn?: string
|
|
690
|
+
Version?: number
|
|
691
|
+
Description?: string
|
|
692
|
+
CompatibleRuntimes?: string[]
|
|
693
|
+
CompatibleArchitectures?: string[]
|
|
694
|
+
}>
|
|
695
|
+
NextMarker?: string
|
|
696
|
+
}> {
|
|
697
|
+
const queryParams: Record<string, string> = {}
|
|
698
|
+
if (params?.CompatibleRuntime) queryParams.CompatibleRuntime = params.CompatibleRuntime
|
|
699
|
+
if (params?.CompatibleArchitecture) queryParams.CompatibleArchitecture = params.CompatibleArchitecture
|
|
700
|
+
if (params?.MaxItems) queryParams.MaxItems = String(params.MaxItems)
|
|
701
|
+
if (params?.Marker) queryParams.Marker = params.Marker
|
|
702
|
+
|
|
703
|
+
const result = await this.client.request({
|
|
704
|
+
service: 'lambda',
|
|
705
|
+
region: this.region,
|
|
706
|
+
method: 'GET',
|
|
707
|
+
path: `/2018-10-31/layers/${encodeURIComponent(layerName)}/versions`,
|
|
708
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
709
|
+
headers: {
|
|
710
|
+
'Content-Type': 'application/json',
|
|
711
|
+
},
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
return result
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Get layer version details
|
|
719
|
+
*/
|
|
720
|
+
async getLayerVersion(layerName: string, versionNumber: number): Promise<{
|
|
721
|
+
LayerArn?: string
|
|
722
|
+
LayerVersionArn?: string
|
|
723
|
+
Description?: string
|
|
724
|
+
Version?: number
|
|
725
|
+
CompatibleRuntimes?: string[]
|
|
726
|
+
CompatibleArchitectures?: string[]
|
|
727
|
+
Content?: {
|
|
728
|
+
Location?: string
|
|
729
|
+
CodeSha256?: string
|
|
730
|
+
CodeSize?: number
|
|
731
|
+
}
|
|
732
|
+
}> {
|
|
733
|
+
const result = await this.client.request({
|
|
734
|
+
service: 'lambda',
|
|
735
|
+
region: this.region,
|
|
736
|
+
method: 'GET',
|
|
737
|
+
path: `/2018-10-31/layers/${encodeURIComponent(layerName)}/versions/${versionNumber}`,
|
|
738
|
+
headers: {
|
|
739
|
+
'Content-Type': 'application/json',
|
|
740
|
+
},
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
return result
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Add permission to a layer version (e.g., make it public)
|
|
748
|
+
*/
|
|
749
|
+
async addLayerVersionPermission(params: {
|
|
750
|
+
LayerName: string
|
|
751
|
+
VersionNumber: number
|
|
752
|
+
StatementId: string
|
|
753
|
+
Action: string
|
|
754
|
+
Principal: string
|
|
755
|
+
OrganizationId?: string
|
|
756
|
+
}): Promise<{ Statement?: string; RevisionId?: string }> {
|
|
757
|
+
const { LayerName, VersionNumber, ...rest } = params
|
|
758
|
+
const result = await this.client.request({
|
|
759
|
+
service: 'lambda',
|
|
760
|
+
region: this.region,
|
|
761
|
+
method: 'POST',
|
|
762
|
+
path: `/2018-10-31/layers/${encodeURIComponent(LayerName)}/versions/${VersionNumber}/policy`,
|
|
763
|
+
headers: {
|
|
764
|
+
'Content-Type': 'application/json',
|
|
765
|
+
},
|
|
766
|
+
body: JSON.stringify(rest),
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
return result
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Delete a layer version
|
|
774
|
+
*/
|
|
775
|
+
async deleteLayerVersion(layerName: string, versionNumber: number): Promise<void> {
|
|
776
|
+
await this.client.request({
|
|
777
|
+
service: 'lambda',
|
|
778
|
+
region: this.region,
|
|
779
|
+
method: 'DELETE',
|
|
780
|
+
path: `/2018-10-31/layers/${encodeURIComponent(layerName)}/versions/${versionNumber}`,
|
|
781
|
+
headers: {
|
|
782
|
+
'Content-Type': 'application/json',
|
|
783
|
+
},
|
|
784
|
+
})
|
|
785
|
+
}
|
|
786
|
+
}
|