@orcapt/cli 1.0.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/LICENSE +22 -0
- package/QUICK_START.md +241 -0
- package/README.md +949 -0
- package/bin/orca.js +406 -0
- package/package.json +58 -0
- package/src/commands/db.js +248 -0
- package/src/commands/fetch-doc.js +220 -0
- package/src/commands/kickstart-node.js +431 -0
- package/src/commands/kickstart-python.js +360 -0
- package/src/commands/lambda.js +736 -0
- package/src/commands/login.js +277 -0
- package/src/commands/storage.js +911 -0
- package/src/commands/ui.js +286 -0
- package/src/config.js +62 -0
- package/src/utils/docker-helper.js +357 -0
- package/src/utils/index.js +349 -0
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orca Lambda Commands
|
|
3
|
+
* Deploy and manage Docker images on AWS Lambda
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const https = require('https');
|
|
9
|
+
const http = require('http');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { getCredentials } = require('./login');
|
|
13
|
+
const { API_BASE_URL, API_ENDPOINTS } = require('../config');
|
|
14
|
+
const {
|
|
15
|
+
checkDockerInstalled,
|
|
16
|
+
checkDockerImage,
|
|
17
|
+
getImageSize,
|
|
18
|
+
pushImageToECR
|
|
19
|
+
} = require('../utils/docker-helper');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Make API request to Orca Deploy API
|
|
23
|
+
*/
|
|
24
|
+
function makeApiRequest(method, endpoint, credentials, body = null) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const url = new URL(endpoint, API_BASE_URL);
|
|
27
|
+
const isHttps = url.protocol === 'https:';
|
|
28
|
+
const httpModule = isHttps ? https : http;
|
|
29
|
+
|
|
30
|
+
const options = {
|
|
31
|
+
hostname: url.hostname,
|
|
32
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
33
|
+
path: url.pathname,
|
|
34
|
+
method: method,
|
|
35
|
+
headers: {
|
|
36
|
+
'x-workspace': credentials.workspace,
|
|
37
|
+
'x-token': credentials.token,
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
'x-mode' : credentials.mode
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const req = httpModule.request(options, (res) => {
|
|
44
|
+
let data = '';
|
|
45
|
+
|
|
46
|
+
res.on('data', (chunk) => {
|
|
47
|
+
data += chunk;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
res.on('end', () => {
|
|
51
|
+
try {
|
|
52
|
+
const response = JSON.parse(data);
|
|
53
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
54
|
+
resolve(response);
|
|
55
|
+
} else {
|
|
56
|
+
reject({ statusCode: res.statusCode, response });
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
reject(new Error(`Invalid response: ${data}`));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
req.on('error', (error) => {
|
|
65
|
+
reject(error);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (body) {
|
|
69
|
+
req.write(JSON.stringify(body));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
req.end();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check authentication
|
|
78
|
+
*/
|
|
79
|
+
function requireAuth() {
|
|
80
|
+
const credentials = getCredentials();
|
|
81
|
+
if (!credentials) {
|
|
82
|
+
console.log(chalk.red('\nā Not authenticated'));
|
|
83
|
+
console.log(chalk.cyan('Please run:'), chalk.yellow('orca login'), chalk.cyan('first\n'));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
return credentials;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Lambda Deploy Command
|
|
91
|
+
*/
|
|
92
|
+
async function lambdaDeploy(functionName, options = {}) {
|
|
93
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
94
|
+
console.log(chalk.cyan('š Deploying Lambda Function'));
|
|
95
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
96
|
+
|
|
97
|
+
const credentials = requireAuth();
|
|
98
|
+
|
|
99
|
+
if (!options.image) {
|
|
100
|
+
console.log(chalk.red('ā Docker image is required'));
|
|
101
|
+
console.log(chalk.cyan('Usage:'), chalk.white('orca lambda deploy <function-name> --image <docker-image>\n'));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Parse environment variables from array to object
|
|
106
|
+
const environmentVars = {};
|
|
107
|
+
|
|
108
|
+
// Read from .env file if provided
|
|
109
|
+
if (options.envFile) {
|
|
110
|
+
try {
|
|
111
|
+
const envFilePath = path.resolve(process.cwd(), options.envFile);
|
|
112
|
+
if (!fs.existsSync(envFilePath)) {
|
|
113
|
+
console.log(chalk.red(`ā Environment file not found: ${envFilePath}\n`));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const envFileContent = fs.readFileSync(envFilePath, 'utf8');
|
|
118
|
+
envFileContent.split('\n').forEach(line => {
|
|
119
|
+
line = line.trim();
|
|
120
|
+
// Skip empty lines and comments
|
|
121
|
+
if (!line || line.startsWith('#')) return;
|
|
122
|
+
|
|
123
|
+
const [key, ...valueParts] = line.split('=');
|
|
124
|
+
if (key && valueParts.length > 0) {
|
|
125
|
+
let value = valueParts.join('=').trim();
|
|
126
|
+
// Remove quotes if present
|
|
127
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
128
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
129
|
+
value = value.slice(1, -1);
|
|
130
|
+
}
|
|
131
|
+
environmentVars[key.trim()] = value;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.log(chalk.red(`ā Failed to read .env file: ${error.message}\n`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Parse from --env flags (these override .env file)
|
|
141
|
+
if (options.env && Array.isArray(options.env)) {
|
|
142
|
+
options.env.forEach(envStr => {
|
|
143
|
+
const [key, ...valueParts] = envStr.split('=');
|
|
144
|
+
if (key && valueParts.length > 0) {
|
|
145
|
+
environmentVars[key.trim()] = valueParts.join('=').trim();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(chalk.white('Function: '), chalk.yellow(functionName));
|
|
151
|
+
console.log(chalk.white('Image: '), chalk.yellow(options.image));
|
|
152
|
+
console.log(chalk.white('Memory: '), chalk.yellow(`${options.memory || 512} MB`));
|
|
153
|
+
console.log(chalk.white('Timeout: '), chalk.yellow(`${options.timeout || 30}s`));
|
|
154
|
+
console.log(chalk.white('Workspace: '), chalk.yellow(credentials.workspace));
|
|
155
|
+
if (Object.keys(environmentVars).length > 0) {
|
|
156
|
+
console.log(chalk.white('Env Vars:'));
|
|
157
|
+
Object.entries(environmentVars).forEach(([key, value]) => {
|
|
158
|
+
console.log(chalk.gray(' '), chalk.cyan(key) + chalk.gray('=') + chalk.yellow(value));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
console.log();
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
// Step 1: Check if Docker is installed
|
|
165
|
+
let spinner = ora('Step 1/9: Checking Docker...').start();
|
|
166
|
+
await checkDockerInstalled();
|
|
167
|
+
spinner.succeed(chalk.green('ā Docker is installed and running'));
|
|
168
|
+
|
|
169
|
+
// Step 2: Check if image exists locally
|
|
170
|
+
spinner = ora('Step 2/9: Checking Docker image...').start();
|
|
171
|
+
const imageExists = await checkDockerImage(options.image);
|
|
172
|
+
if (!imageExists) {
|
|
173
|
+
spinner.fail(chalk.red('ā Docker image not found locally'));
|
|
174
|
+
console.log(chalk.yellow('\nPlease build the image first:'));
|
|
175
|
+
console.log(chalk.white(' docker build -t'), chalk.cyan(options.image), chalk.white('.'));
|
|
176
|
+
console.log();
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const imageSize = await getImageSize(options.image);
|
|
180
|
+
spinner.succeed(chalk.green(`ā Found image '${options.image}' (${imageSize})`));
|
|
181
|
+
|
|
182
|
+
// Step 3: Request ECR credentials from API
|
|
183
|
+
spinner = ora('Step 3/9: Requesting ECR credentials...').start();
|
|
184
|
+
|
|
185
|
+
let deploymentRequest;
|
|
186
|
+
try {
|
|
187
|
+
deploymentRequest = await makeApiRequest('POST', API_ENDPOINTS.LAMBDA_DEPLOY, credentials, {
|
|
188
|
+
function_name: functionName,
|
|
189
|
+
image_tag: options.image,
|
|
190
|
+
memory_mb: options.memory || 512,
|
|
191
|
+
timeout_seconds: options.timeout || 30,
|
|
192
|
+
environment_vars: environmentVars
|
|
193
|
+
});
|
|
194
|
+
} catch (error) {
|
|
195
|
+
spinner.fail(chalk.red('ā Failed to request ECR credentials'));
|
|
196
|
+
console.log();
|
|
197
|
+
|
|
198
|
+
if (error.statusCode) {
|
|
199
|
+
console.log(chalk.red('Status Code:'), error.statusCode);
|
|
200
|
+
console.log(chalk.red('Response:'), JSON.stringify(error.response, null, 2));
|
|
201
|
+
} else if (error.message) {
|
|
202
|
+
console.log(chalk.red('Error:'), error.message);
|
|
203
|
+
} else {
|
|
204
|
+
console.log(chalk.red('Error:'), error);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(chalk.yellow('š” Troubleshooting tips:'));
|
|
209
|
+
console.log(chalk.white(' 1. Check if backend is running'));
|
|
210
|
+
console.log(chalk.white(' 2. Verify your authentication: orca whoami'));
|
|
211
|
+
console.log(chalk.white(' 3. Check backend logs for errors'));
|
|
212
|
+
console.log(chalk.white(' 4. Ensure AWS credentials are configured in backend .env'));
|
|
213
|
+
console.log();
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!deploymentRequest.ecr_url || !deploymentRequest.repository_uri) {
|
|
218
|
+
spinner.fail(chalk.red('ā Invalid response from API'));
|
|
219
|
+
console.log(chalk.yellow('\nAPI Response:'), deploymentRequest);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
spinner.succeed(chalk.green('ā ECR credentials received'));
|
|
224
|
+
console.log(chalk.gray(` Repository: ${deploymentRequest.repository_uri}`));
|
|
225
|
+
|
|
226
|
+
// Step 4-6: Push image to ECR (handled by helper)
|
|
227
|
+
const remoteImage = await pushImageToECR(
|
|
228
|
+
options.image,
|
|
229
|
+
deploymentRequest.ecr_url,
|
|
230
|
+
deploymentRequest.ecr_username,
|
|
231
|
+
deploymentRequest.ecr_password,
|
|
232
|
+
deploymentRequest.repository_uri
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Step 7: Notify API that image is pushed
|
|
236
|
+
spinner = ora('Step 7/9: Creating Lambda function...').start();
|
|
237
|
+
const confirmResponse = await makeApiRequest(
|
|
238
|
+
'POST',
|
|
239
|
+
`${API_ENDPOINTS.LAMBDA_DEPLOY}/confirm`,
|
|
240
|
+
credentials,
|
|
241
|
+
{
|
|
242
|
+
deployment_id: deploymentRequest.deployment_id,
|
|
243
|
+
function_name: functionName,
|
|
244
|
+
image_uri: remoteImage
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
spinner.succeed(chalk.green('ā Lambda function created'));
|
|
248
|
+
|
|
249
|
+
// Step 8: Create API Gateway (if configured)
|
|
250
|
+
if (confirmResponse.invoke_url) {
|
|
251
|
+
spinner = ora('Step 8/9: Configuring API Gateway...').start();
|
|
252
|
+
spinner.succeed(chalk.green('ā API Gateway configured'));
|
|
253
|
+
} else {
|
|
254
|
+
console.log(chalk.gray(' ā API Gateway not configured'));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Step 9: Save to database
|
|
258
|
+
spinner = ora('Step 9/9: Saving deployment info...').start();
|
|
259
|
+
spinner.succeed(chalk.green('ā Deployment completed'));
|
|
260
|
+
|
|
261
|
+
// Success message
|
|
262
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
263
|
+
console.log(chalk.green('ā Function deployed successfully!'));
|
|
264
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
265
|
+
|
|
266
|
+
console.log(chalk.white('Function Details:'));
|
|
267
|
+
console.log(chalk.white(' Name: '), chalk.yellow(functionName));
|
|
268
|
+
console.log(chalk.white(' Image: '), chalk.yellow(remoteImage));
|
|
269
|
+
console.log(chalk.white(' Region: '), chalk.yellow(confirmResponse.region || 'us-east-1'));
|
|
270
|
+
console.log(chalk.white(' Memory: '), chalk.yellow(`${options.memory || 512} MB`));
|
|
271
|
+
console.log(chalk.white(' Timeout: '), chalk.yellow(`${options.timeout || 30}s`));
|
|
272
|
+
console.log(chalk.white(' Status: '), chalk.green('Active'));
|
|
273
|
+
|
|
274
|
+
if (confirmResponse.invoke_url) {
|
|
275
|
+
console.log(chalk.cyan('\nInvoke URL:'));
|
|
276
|
+
console.log(chalk.white(' '), chalk.yellow(confirmResponse.invoke_url));
|
|
277
|
+
console.log(chalk.cyan('\nTry it:'));
|
|
278
|
+
console.log(chalk.white(' curl'), chalk.cyan(confirmResponse.invoke_url));
|
|
279
|
+
console.log(chalk.white(' orca lambda invoke'), chalk.cyan(functionName));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (confirmResponse.sqs_queue_url) {
|
|
283
|
+
console.log(chalk.cyan('\nSQS Queue (for async processing):'));
|
|
284
|
+
console.log(chalk.white(' '), chalk.yellow(confirmResponse.sqs_queue_url));
|
|
285
|
+
console.log(chalk.gray(' Note: SQS_QUEUE_URL is automatically set as an env variable'));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log(chalk.cyan('\n============================================================\n'));
|
|
289
|
+
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.log(chalk.red('\nā Deployment failed'));
|
|
292
|
+
|
|
293
|
+
if (error.statusCode === 401) {
|
|
294
|
+
console.log(chalk.red('Authentication failed'));
|
|
295
|
+
console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orca login\n'));
|
|
296
|
+
} else if (error.statusCode === 404) {
|
|
297
|
+
console.log(chalk.red('Endpoint not found'));
|
|
298
|
+
console.log(chalk.yellow('The Lambda API may not be implemented yet.'));
|
|
299
|
+
console.log(chalk.cyan('See STORAGE_LAMBDA_ARCHITECTURE.md for details\n'));
|
|
300
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
301
|
+
console.log(chalk.red('Connection refused'));
|
|
302
|
+
console.log(chalk.yellow('Cannot connect to Orca API:'), chalk.white(API_BASE_URL));
|
|
303
|
+
console.log(chalk.cyan('Make sure the backend is running.\n'));
|
|
304
|
+
} else {
|
|
305
|
+
console.log(chalk.red(`Error: ${error.message}\n`));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Lambda List Command
|
|
314
|
+
*/
|
|
315
|
+
async function lambdaList() {
|
|
316
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
317
|
+
console.log(chalk.cyan('š Listing Lambda Functions'));
|
|
318
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
319
|
+
|
|
320
|
+
const credentials = requireAuth();
|
|
321
|
+
console.log(chalk.white('Workspace:'), chalk.yellow(credentials.workspace));
|
|
322
|
+
|
|
323
|
+
const spinner = ora('Fetching functions...').start();
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
// Call backend API to get list of functions
|
|
327
|
+
const response = await makeApiRequest('GET', API_ENDPOINTS.LAMBDA_LIST, credentials);
|
|
328
|
+
|
|
329
|
+
spinner.succeed(chalk.green('Functions retrieved'));
|
|
330
|
+
|
|
331
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
332
|
+
|
|
333
|
+
if (response.count === 0) {
|
|
334
|
+
console.log(chalk.yellow('No Lambda functions found'));
|
|
335
|
+
console.log(chalk.cyan('\nCreate one with:'), chalk.white('orca lambda deploy <function-name> --image <docker-image>'));
|
|
336
|
+
} else {
|
|
337
|
+
console.log(chalk.green(`ā Found ${response.count} function${response.count > 1 ? 's' : ''}`));
|
|
338
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
339
|
+
|
|
340
|
+
response.functions.forEach((func, index) => {
|
|
341
|
+
console.log(chalk.white(` ${index + 1}. ${func.function_name}`));
|
|
342
|
+
console.log(chalk.gray(` Image: ${func.image_uri || 'N/A'}`));
|
|
343
|
+
console.log(chalk.gray(` Status: ${func.status}`));
|
|
344
|
+
console.log(chalk.gray(` Memory: ${func.memory_mb} MB`));
|
|
345
|
+
console.log(chalk.gray(` Timeout: ${func.timeout_seconds}s`));
|
|
346
|
+
if (func.invoke_url) {
|
|
347
|
+
console.log(chalk.gray(` URL: ${func.invoke_url}`));
|
|
348
|
+
}
|
|
349
|
+
if (func.sqs_queue_url) {
|
|
350
|
+
console.log(chalk.gray(` SQS: ${func.sqs_queue_url}`));
|
|
351
|
+
}
|
|
352
|
+
if (func.deployed_at) {
|
|
353
|
+
console.log(chalk.gray(` Deployed: ${new Date(func.deployed_at).toLocaleString()}`));
|
|
354
|
+
}
|
|
355
|
+
console.log();
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
360
|
+
|
|
361
|
+
} catch (error) {
|
|
362
|
+
spinner.fail(chalk.red('Failed to list functions'));
|
|
363
|
+
|
|
364
|
+
if (error.statusCode === 401) {
|
|
365
|
+
console.log(chalk.red('\nā Authentication failed'));
|
|
366
|
+
console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orca login\n'));
|
|
367
|
+
} else if (error.statusCode === 404) {
|
|
368
|
+
console.log(chalk.red('\nā Endpoint not found'));
|
|
369
|
+
console.log(chalk.yellow('The Lambda API may not be implemented yet.\n'));
|
|
370
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
371
|
+
console.log(chalk.red('\nā Connection refused'));
|
|
372
|
+
console.log(chalk.yellow('Cannot connect to Orca API:'), chalk.white(API_BASE_URL));
|
|
373
|
+
console.log(chalk.cyan('Make sure the backend is running.\n'));
|
|
374
|
+
} else {
|
|
375
|
+
console.log(chalk.red(`\nā ${error.message}\n`));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Lambda Invoke Command
|
|
384
|
+
*/
|
|
385
|
+
async function lambdaInvoke(functionName, options = {}) {
|
|
386
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
387
|
+
console.log(chalk.cyan('ā¶ļø Invoking Lambda Function'));
|
|
388
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
389
|
+
|
|
390
|
+
const credentials = requireAuth();
|
|
391
|
+
|
|
392
|
+
console.log(chalk.white('Function: '), chalk.yellow(functionName));
|
|
393
|
+
|
|
394
|
+
// Parse payload if provided
|
|
395
|
+
let payload = {};
|
|
396
|
+
if (options.payload) {
|
|
397
|
+
try {
|
|
398
|
+
payload = JSON.parse(options.payload);
|
|
399
|
+
console.log(chalk.white('Payload: '), chalk.yellow(JSON.stringify(payload)));
|
|
400
|
+
} catch (e) {
|
|
401
|
+
console.log(chalk.red('\nā Invalid JSON payload'));
|
|
402
|
+
console.log(chalk.yellow('Please provide valid JSON:'), chalk.white('--payload \'{"key": "value"}\'\n'));
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const spinner = ora('Invoking function...').start();
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
// Call backend API to invoke Lambda function
|
|
411
|
+
const startTime = Date.now();
|
|
412
|
+
const endpoint = API_ENDPOINTS.LAMBDA_INVOKE.replace('{functionName}', functionName);
|
|
413
|
+
const response = await makeApiRequest(
|
|
414
|
+
'POST',
|
|
415
|
+
endpoint,
|
|
416
|
+
credentials,
|
|
417
|
+
{ payload }
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
const duration = Date.now() - startTime;
|
|
421
|
+
|
|
422
|
+
spinner.succeed(chalk.green('Function invoked successfully'));
|
|
423
|
+
|
|
424
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
425
|
+
console.log(chalk.green('ā Invocation Result'));
|
|
426
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
427
|
+
|
|
428
|
+
console.log(chalk.white('Status: '), chalk.green(response.statusCode || 200));
|
|
429
|
+
console.log(chalk.white('Duration: '), chalk.yellow(`${duration}ms`));
|
|
430
|
+
|
|
431
|
+
if (response.executedVersion) {
|
|
432
|
+
console.log(chalk.white('Version: '), chalk.yellow(response.executedVersion));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
console.log(chalk.cyan('\nResponse:'));
|
|
436
|
+
console.log(chalk.white(JSON.stringify(response.response || response, null, 2)));
|
|
437
|
+
|
|
438
|
+
console.log(chalk.cyan('\n============================================================\n'));
|
|
439
|
+
|
|
440
|
+
} catch (error) {
|
|
441
|
+
spinner.fail(chalk.red('Invocation failed'));
|
|
442
|
+
|
|
443
|
+
if (error.statusCode === 401) {
|
|
444
|
+
console.log(chalk.red('\nā Authentication failed'));
|
|
445
|
+
console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orca login\n'));
|
|
446
|
+
} else if (error.statusCode === 404) {
|
|
447
|
+
console.log(chalk.red('\nā Function not found'));
|
|
448
|
+
console.log(chalk.yellow(`Function '${functionName}' does not exist or doesn't belong to your workspace.\n`));
|
|
449
|
+
} else if (error.statusCode === 500) {
|
|
450
|
+
console.log(chalk.red('\nā Function execution error'));
|
|
451
|
+
if (error.response && error.response.error) {
|
|
452
|
+
console.log(chalk.yellow('Error:'), chalk.white(error.response.error));
|
|
453
|
+
}
|
|
454
|
+
console.log();
|
|
455
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
456
|
+
console.log(chalk.red('\nā Connection refused'));
|
|
457
|
+
console.log(chalk.yellow('Cannot connect to Orca API:'), chalk.white(API_BASE_URL));
|
|
458
|
+
console.log(chalk.cyan('Make sure the backend is running.\n'));
|
|
459
|
+
} else {
|
|
460
|
+
console.log(chalk.red(`\nā ${error.message}\n`));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Lambda Logs Command
|
|
469
|
+
*/
|
|
470
|
+
async function lambdaLogs(functionName, options = {}) {
|
|
471
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
472
|
+
console.log(chalk.cyan('š Lambda Function Logs'));
|
|
473
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
474
|
+
|
|
475
|
+
const credentials = requireAuth();
|
|
476
|
+
|
|
477
|
+
console.log(chalk.white('Function: '), chalk.yellow(functionName));
|
|
478
|
+
|
|
479
|
+
if (options.since) {
|
|
480
|
+
console.log(chalk.white('Since: '), chalk.yellow(options.since));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (options.tail) {
|
|
484
|
+
console.log(chalk.white('Mode: '), chalk.yellow('Live streaming'));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const spinner = ora('Fetching logs...').start();
|
|
488
|
+
const endpoint = API_ENDPOINTS.LAMBDA_LOGS.replace('{functionName}', functionName);
|
|
489
|
+
try {
|
|
490
|
+
// Call backend API to get logs
|
|
491
|
+
const response = await makeApiRequest(
|
|
492
|
+
'GET',
|
|
493
|
+
endpoint,
|
|
494
|
+
credentials
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
spinner.succeed(chalk.green('Logs retrieved'));
|
|
498
|
+
|
|
499
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
500
|
+
|
|
501
|
+
if (!response.logs || response.logs.length === 0) {
|
|
502
|
+
console.log(chalk.yellow('No logs found'));
|
|
503
|
+
console.log(chalk.cyan('\nTry invoking the function first:'));
|
|
504
|
+
console.log(chalk.white(' orca lambda invoke'), chalk.cyan(functionName));
|
|
505
|
+
} else {
|
|
506
|
+
console.log(chalk.green(`ā Found ${response.logs.length} log entries`));
|
|
507
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
508
|
+
|
|
509
|
+
response.logs.forEach(log => {
|
|
510
|
+
const timestamp = new Date(log.timestamp).toLocaleString();
|
|
511
|
+
const level = log.level.toUpperCase();
|
|
512
|
+
|
|
513
|
+
let levelColor = chalk.white;
|
|
514
|
+
if (level === 'ERROR') levelColor = chalk.red;
|
|
515
|
+
else if (level === 'WARNING') levelColor = chalk.yellow;
|
|
516
|
+
else if (level === 'INFO') levelColor = chalk.blue;
|
|
517
|
+
|
|
518
|
+
console.log(
|
|
519
|
+
chalk.gray(`[${timestamp}]`),
|
|
520
|
+
levelColor(`${level}:`),
|
|
521
|
+
chalk.white(log.message)
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
527
|
+
|
|
528
|
+
if (options.tail) {
|
|
529
|
+
console.log(chalk.yellow('\nā Live streaming not yet supported'));
|
|
530
|
+
console.log(chalk.cyan('Showing latest logs only\n'));
|
|
531
|
+
} else {
|
|
532
|
+
console.log();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
} catch (error) {
|
|
536
|
+
spinner.fail(chalk.red('Failed to fetch logs'));
|
|
537
|
+
|
|
538
|
+
if (error.statusCode === 401) {
|
|
539
|
+
console.log(chalk.red('\nā Authentication failed'));
|
|
540
|
+
console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orca login\n'));
|
|
541
|
+
} else if (error.statusCode === 404) {
|
|
542
|
+
console.log(chalk.red('\nā Function not found'));
|
|
543
|
+
console.log(chalk.yellow(`Function '${functionName}' does not exist or doesn't belong to your workspace.\n`));
|
|
544
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
545
|
+
console.log(chalk.red('\nā Connection refused'));
|
|
546
|
+
console.log(chalk.yellow('Cannot connect to Orca API:'), chalk.white(API_BASE_URL));
|
|
547
|
+
console.log(chalk.cyan('Make sure the backend is running.\n'));
|
|
548
|
+
} else {
|
|
549
|
+
console.log(chalk.red(`\nā ${error.message}\n`));
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
process.exit(1);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Lambda Remove Command
|
|
558
|
+
*/
|
|
559
|
+
async function lambdaRemove(functionName) {
|
|
560
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
561
|
+
console.log(chalk.cyan('šļø Removing Lambda Function'));
|
|
562
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
563
|
+
|
|
564
|
+
const credentials = requireAuth();
|
|
565
|
+
|
|
566
|
+
console.log(chalk.white('Function: '), chalk.yellow(functionName));
|
|
567
|
+
console.log(chalk.white('Workspace: '), chalk.yellow(credentials.workspace));
|
|
568
|
+
|
|
569
|
+
// Confirmation prompt
|
|
570
|
+
console.log(chalk.red('\nā ļø WARNING: This will permanently delete the function!'));
|
|
571
|
+
console.log(chalk.yellow('The Lambda function and all its configurations will be removed.'));
|
|
572
|
+
console.log(chalk.yellow('ECR repository will be kept for potential rollback.\n'));
|
|
573
|
+
|
|
574
|
+
const spinner = ora('Removing function...').start();
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
// Call backend API to delete Lambda function
|
|
578
|
+
const response = await makeApiRequest(
|
|
579
|
+
'DELETE',
|
|
580
|
+
`${API_ENDPOINTS.LAMBDA_DELETE}/${functionName}`,
|
|
581
|
+
credentials
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
spinner.succeed(chalk.green('Function removed successfully'));
|
|
585
|
+
|
|
586
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
587
|
+
console.log(chalk.green('ā Function Removed'));
|
|
588
|
+
console.log(chalk.cyan('============================================================'));
|
|
589
|
+
console.log(chalk.white('Function: '), chalk.yellow(functionName));
|
|
590
|
+
console.log(chalk.white('Workspace: '), chalk.yellow(credentials.workspace));
|
|
591
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
592
|
+
|
|
593
|
+
console.log(chalk.gray('Note: ECR repository has been kept for potential rollback.'));
|
|
594
|
+
console.log(chalk.gray('You can manually clean it up if needed.\n'));
|
|
595
|
+
|
|
596
|
+
} catch (error) {
|
|
597
|
+
spinner.fail(chalk.red('Failed to remove function'));
|
|
598
|
+
|
|
599
|
+
if (error.statusCode === 401) {
|
|
600
|
+
console.log(chalk.red('\nā Authentication failed'));
|
|
601
|
+
console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orca login\n'));
|
|
602
|
+
} else if (error.statusCode === 404) {
|
|
603
|
+
console.log(chalk.red('\nā Function not found'));
|
|
604
|
+
console.log(chalk.yellow(`Function '${functionName}' does not exist or doesn't belong to your workspace.\n`));
|
|
605
|
+
} else if (error.statusCode === 409) {
|
|
606
|
+
console.log(chalk.red('\nā Conflict'));
|
|
607
|
+
console.log(chalk.yellow('Function may be currently in use. Please try again later.\n'));
|
|
608
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
609
|
+
console.log(chalk.red('\nā Connection refused'));
|
|
610
|
+
console.log(chalk.yellow('Cannot connect to Orca API:'), chalk.white(API_BASE_URL));
|
|
611
|
+
console.log(chalk.cyan('Make sure the backend is running.\n'));
|
|
612
|
+
} else {
|
|
613
|
+
console.log(chalk.red(`\nā ${error.message}\n`));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Lambda Info Command - Get function details
|
|
622
|
+
*/
|
|
623
|
+
async function lambdaInfo(functionName) {
|
|
624
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
625
|
+
console.log(chalk.cyan('š Lambda Function Details'));
|
|
626
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
627
|
+
|
|
628
|
+
const credentials = requireAuth();
|
|
629
|
+
|
|
630
|
+
const spinner = ora('Fetching function details...').start();
|
|
631
|
+
|
|
632
|
+
try {
|
|
633
|
+
// Call backend API to get function details
|
|
634
|
+
const response = await makeApiRequest(
|
|
635
|
+
'GET',
|
|
636
|
+
`${API_ENDPOINTS.LAMBDA_INFO}/${functionName}`,
|
|
637
|
+
credentials
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
spinner.succeed(chalk.green('Function details retrieved'));
|
|
641
|
+
|
|
642
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
643
|
+
console.log(chalk.green('ā Function Information'));
|
|
644
|
+
console.log(chalk.cyan('============================================================\n'));
|
|
645
|
+
|
|
646
|
+
console.log(chalk.white('Name: '), chalk.yellow(response.function_name));
|
|
647
|
+
console.log(chalk.white('Status: '),
|
|
648
|
+
response.status === 'active' ? chalk.green(response.status) : chalk.yellow(response.status)
|
|
649
|
+
);
|
|
650
|
+
console.log(chalk.white('Image: '), chalk.yellow(response.image_uri || 'N/A'));
|
|
651
|
+
console.log(chalk.white('Region: '), chalk.yellow(response.aws_region || 'us-east-1'));
|
|
652
|
+
console.log(chalk.white('Memory: '), chalk.yellow(`${response.memory_mb} MB`));
|
|
653
|
+
console.log(chalk.white('Timeout: '), chalk.yellow(`${response.timeout_seconds}s`));
|
|
654
|
+
|
|
655
|
+
if (response.function_arn) {
|
|
656
|
+
console.log(chalk.white('Function ARN: '), chalk.gray(response.function_arn));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (response.created_at) {
|
|
660
|
+
console.log(chalk.white('Created: '), chalk.yellow(new Date(response.created_at).toLocaleString()));
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (response.deployed_at) {
|
|
664
|
+
console.log(chalk.white('Last Deploy: '), chalk.yellow(new Date(response.deployed_at).toLocaleString()));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Environment Variables
|
|
668
|
+
if (response.environment_vars && Object.keys(response.environment_vars).length > 0) {
|
|
669
|
+
console.log(chalk.cyan('\nEnvironment Variables:'));
|
|
670
|
+
Object.entries(response.environment_vars).forEach(([key, value]) => {
|
|
671
|
+
// Hide sensitive values
|
|
672
|
+
const displayValue = key.toLowerCase().includes('password') ||
|
|
673
|
+
key.toLowerCase().includes('secret') ||
|
|
674
|
+
key.toLowerCase().includes('key')
|
|
675
|
+
? '***hidden***' : value;
|
|
676
|
+
console.log(chalk.white(` ${key}:`.padEnd(20)), chalk.yellow(displayValue));
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Invoke URL
|
|
681
|
+
if (response.invoke_url) {
|
|
682
|
+
console.log(chalk.cyan('\nInvoke URL:'));
|
|
683
|
+
console.log(chalk.white(' '), chalk.yellow(response.invoke_url));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Metrics (if available)
|
|
687
|
+
if (response.metrics) {
|
|
688
|
+
console.log(chalk.cyan('\nMetrics (Last 24h):'));
|
|
689
|
+
console.log(chalk.white(' Invocations: '), chalk.yellow(response.metrics.invocations || 0));
|
|
690
|
+
console.log(chalk.white(' Errors: '), chalk.yellow(response.metrics.errors || 0));
|
|
691
|
+
if (response.metrics.avg_duration) {
|
|
692
|
+
console.log(chalk.white(' Avg Duration:'), chalk.yellow(`${response.metrics.avg_duration}ms`));
|
|
693
|
+
}
|
|
694
|
+
if (response.metrics.total_cost) {
|
|
695
|
+
console.log(chalk.white(' Total Cost: '), chalk.yellow(`$${response.metrics.total_cost}`));
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
console.log(chalk.cyan('\n============================================================'));
|
|
700
|
+
console.log(chalk.gray('\nCommands:'));
|
|
701
|
+
console.log(chalk.white(' Invoke: '), chalk.cyan(`orca lambda invoke ${functionName}`));
|
|
702
|
+
console.log(chalk.white(' Logs: '), chalk.cyan(`orca lambda logs ${functionName}`));
|
|
703
|
+
console.log(chalk.white(' Update: '), chalk.cyan(`orca lambda update ${functionName} --image new-image:tag`));
|
|
704
|
+
console.log(chalk.white(' Remove: '), chalk.cyan(`orca lambda remove ${functionName}`));
|
|
705
|
+
console.log();
|
|
706
|
+
|
|
707
|
+
} catch (error) {
|
|
708
|
+
spinner.fail(chalk.red('Failed to fetch function details'));
|
|
709
|
+
|
|
710
|
+
if (error.statusCode === 401) {
|
|
711
|
+
console.log(chalk.red('\nā Authentication failed'));
|
|
712
|
+
console.log(chalk.yellow('Your session may have expired. Please run:'), chalk.white('orca login\n'));
|
|
713
|
+
} else if (error.statusCode === 404) {
|
|
714
|
+
console.log(chalk.red('\nā Function not found'));
|
|
715
|
+
console.log(chalk.yellow(`Function '${functionName}' does not exist or doesn't belong to your workspace.\n`));
|
|
716
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
717
|
+
console.log(chalk.red('\nā Connection refused'));
|
|
718
|
+
console.log(chalk.yellow('Cannot connect to Orca API:'), chalk.white(API_BASE_URL));
|
|
719
|
+
console.log(chalk.cyan('Make sure the backend is running.\n'));
|
|
720
|
+
} else {
|
|
721
|
+
console.log(chalk.red(`\nā ${error.message}\n`));
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
module.exports = {
|
|
729
|
+
lambdaDeploy,
|
|
730
|
+
lambdaList,
|
|
731
|
+
lambdaInvoke,
|
|
732
|
+
lambdaLogs,
|
|
733
|
+
lambdaRemove,
|
|
734
|
+
lambdaInfo
|
|
735
|
+
};
|
|
736
|
+
|