Skip to content

Commit c8cc60c

Browse files
authored
Merge pull request #2 from fgiova/feature/lambda-plugin
Add full Lambda service plugin with CRUD, triggers, and documentation
2 parents 7530348 + c3eb255 commit c8cc60c

32 files changed

Lines changed: 5734 additions & 22 deletions

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ LocalStack Explorer provides an AWS Console-like experience for your local devel
1212
| SQS | Fully implemented | Queue management, message operations, queue attributes, purge |
1313
| SNS | Fully implemented | Topics, subscriptions, publish, tags, filter policies |
1414
| IAM | Fully implemented | Users, groups, managed/inline policies, access keys, versioning |
15+
| Lambda | Fully implemented | Functions CRUD, invoke, triggers, code/config update, versions, aliases |
1516
| CloudFormation | Fully implemented | Stack CRUD, update, template editor, events, cross-service links |
1617
| DynamoDB | Fully implemented | Table management, create, list, detail views |
1718

1819
## Quick Start
1920

2021
### Prerequisites
2122

22-
- **Node.js** >= 20
23+
- **Node.js** >= 24 (see `.nvmrc`)
2324
- **pnpm** >= 9
2425
- **Docker** (for LocalStack)
2526

@@ -37,7 +38,7 @@ pnpm install
3738
docker compose up -d
3839
```
3940

40-
This starts LocalStack with all required services (S3, SQS, SNS, IAM, CloudFormation, DynamoDB) on `http://localhost:4566`.
41+
This starts LocalStack with all required services (S3, SQS, SNS, IAM, Lambda, CloudFormation, DynamoDB) on `http://localhost:4566`.
4142

4243
### Development
4344

@@ -118,7 +119,7 @@ The backend uses [env-schema](https://github.com/fastify/env-schema) for environ
118119
| `PORT` | `3001` | Backend server port |
119120
| `LOCALSTACK_ENDPOINT` | `http://localhost:4566` | Default LocalStack endpoint URL |
120121
| `LOCALSTACK_REGION` | `us-east-1` | Default AWS region for LocalStack clients |
121-
| `ENABLED_SERVICES` | `s3,sqs,sns,iam,cloudformation,dynamodb` | Comma-separated list of enabled services |
122+
| `ENABLED_SERVICES` | `s3,sqs,sns,iam,lambda,cloudformation,dynamodb` | Comma-separated list of enabled services |
122123

123124
Create a `.env` file in `packages/backend/` to override defaults.
124125

@@ -142,10 +143,10 @@ By default, only a subset of services is enabled. You can control which services
142143
ENABLED_SERVICES=s3,sqs
143144

144145
# Enable all available services
145-
ENABLED_SERVICES=s3,sqs,sns,iam,cloudformation,dynamodb
146+
ENABLED_SERVICES=s3,sqs,sns,iam,lambda,cloudformation,dynamodb
146147
```
147148

148-
Available service names: `s3`, `sqs`, `sns`, `iam`, `cloudformation`, `dynamodb`.
149+
Available service names: `s3`, `sqs`, `sns`, `iam`, `lambda`, `cloudformation`, `dynamodb`.
149150

150151
When a service is disabled:
151152
- Its backend API routes are **not registered** (requests return 404)
@@ -154,6 +155,10 @@ When a service is disabled:
154155

155156
The frontend fetches the list of enabled services from the `GET /api/services` endpoint at startup and filters the UI accordingly.
156157

158+
### Active Service Detection
159+
160+
The health endpoint (`GET /api/health`) queries LocalStack's native `/_localstack/health` API and returns the list of services that are actually running. The frontend uses this data (refreshed every 30 seconds) to visually disable services that are configured but not currently active on the LocalStack instance — they appear greyed out and are not clickable.
161+
157162
## Project Structure
158163

159164
```
@@ -162,7 +167,8 @@ localstack-explorer/
162167
├── packages/
163168
│ ├── backend/ # Fastify API server
164169
│ │ └── src/
165-
│ │ ├── index.ts # Entry point (autoload plugins, serves frontend)
170+
│ │ ├── index.ts # App factory (autoload plugins, serves frontend)
171+
│ │ ├── server.ts # Server entry point (starts listening)
166172
│ │ ├── bundle.ts # Bundle entry point (explicit plugin imports)
167173
│ │ ├── config.ts # env-schema configuration
168174
│ │ ├── health.ts # LocalStack connectivity check
@@ -172,6 +178,7 @@ localstack-explorer/
172178
│ │ │ ├── sqs/ # Complete implementation
173179
│ │ │ ├── sns/ # Complete implementation
174180
│ │ │ ├── iam/ # Complete implementation
181+
│ │ │ ├── lambda/ # Complete implementation
175182
│ │ │ ├── cloudformation/ # Complete implementation
176183
│ │ │ └── dynamodb/ # Complete implementation
177184
│ │ └── shared/ # Error handling, shared types
@@ -219,6 +226,7 @@ localstack-explorer/
219226
- **[SQS Service Guide](docs/sqs.md)** — Complete reference for SQS operations: queue management, message send/receive/delete, queue attributes, and purge.
220227
- **[SNS Service Guide](docs/sns.md)** — Complete reference for SNS operations: topics, subscriptions, publish (single/batch), filter policies, and tags.
221228
- **[IAM Service Guide](docs/iam.md)** — Complete reference for IAM operations: users, groups, managed/inline policies, access keys, policy versioning, and cascading deletes.
229+
- **[Lambda Service Guide](docs/lambda.md)** — Complete reference for Lambda operations: functions CRUD, invoke with log output, triggers (S3, SQS, SNS, etc.), code/config updates, versions, and aliases.
222230
- **[CloudFormation Service Guide](docs/cloudformation.md)** — Complete reference for CloudFormation operations: stack CRUD, update, template editor, events timeline, and cross-service resource navigation.
223231
- **[DynamoDB Service Guide](docs/dynamodb.md)** — Complete reference for DynamoDB operations: table management, creation, listing, and detail views.
224232
- **[Adding New Services](docs/adding-new-services.md)** — Step-by-step guide to implement a new AWS service following the established plugin pattern.

docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ services:
44
ports:
55
- "4566:4566"
66
environment:
7-
- SERVICES=${ENABLED_SERVICES:-s3,sqs,sns,iam,cloudformation,dynamodb}
7+
- SERVICES=${ENABLED_SERVICES:-s3,sqs,sns,iam,lambda,cloudformation,dynamodb}
88
- DEBUG=0
99
- EAGER_SERVICE_LOADING=1
1010
volumes:

docs/lambda.md

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# Lambda Service Guide
2+
3+
Lambda is a fully implemented service in LocalStack Explorer. It supports function management, code and configuration updates, synchronous invocation with log output, triggers, versions, and aliases.
4+
5+
## Features
6+
7+
- List, create, and delete Lambda functions
8+
- View function details (configuration, environment variables, layers, architectures)
9+
- Update function code (zip upload or S3 reference)
10+
- Update function configuration (handler, runtime, memory, timeout, environment, role)
11+
- Invoke functions synchronously with JSON payload
12+
- View invocation results: status code, response payload, function errors, and decoded CloudWatch logs
13+
- **Triggers**: view all trigger sources for a function
14+
- **Resource-based policy triggers** (S3, SNS, API Gateway, EventBridge, etc.) — detected from the function's resource policy via `GetPolicy`
15+
- **Event source mappings** (SQS, DynamoDB Streams, Kinesis, etc.) — with create and delete support
16+
- Browse function versions
17+
- Browse function aliases
18+
- Search/filter functions by name
19+
- Active service detection: Lambda appears greyed out in the UI when not running on LocalStack
20+
21+
## API Endpoints
22+
23+
All endpoints are prefixed with `/api/lambda`.
24+
25+
### Functions
26+
27+
| Method | Path | Description | Request | Response |
28+
|--------|-------------------------------|--------------------------|--------------------------------|--------------------------------|
29+
| GET | `/` | List functions | `?marker=string` | `{ functions: [...] }` |
30+
| POST | `/` | Create function | `CreateFunctionBody` | `{ message: string }` |
31+
| GET | `/:functionName` | Get function detail | -- | `FunctionDetail` |
32+
| PUT | `/:functionName/code` | Update function code | `{ zipFile?, s3Bucket?, s3Key? }` | `{ message: string }` |
33+
| PUT | `/:functionName/config` | Update function config | `UpdateFunctionConfigBody` | `{ message: string }` |
34+
| DELETE | `/:functionName` | Delete function | -- | `{ success: boolean }` |
35+
36+
### Invocation
37+
38+
| Method | Path | Description | Request | Response |
39+
|--------|-------------------------------|--------------------------|--------------------------------|--------------------------------|
40+
| POST | `/:functionName/invoke` | Invoke function | `{ payload?, invocationType? }` | `InvokeFunctionResponse` |
41+
42+
### Triggers
43+
44+
| Method | Path | Description | Request | Response |
45+
|--------|--------------------------------------------|--------------------------------|--------------------------------------------------|-------------------------------------------|
46+
| GET | `/:functionName/triggers` | List all triggers | `?marker=string` | `{ eventSourceMappings, policyTriggers }` |
47+
| POST | `/:functionName/event-source-mappings` | Create event source mapping | `{ eventSourceArn, batchSize?, enabled?, ... }` | `{ message, uuid }` |
48+
| DELETE | `/event-source-mappings/:uuid` | Delete event source mapping | -- | `{ success: boolean }` |
49+
50+
The `GET /:functionName/triggers` endpoint combines two data sources:
51+
- **Event source mappings** — Lambda's `ListEventSourceMappings` (SQS queues, DynamoDB Streams, Kinesis streams)
52+
- **Policy triggers** — parsed from the function's resource-based policy via `GetPolicy` (S3 bucket notifications, SNS topics, API Gateway, EventBridge rules, etc.)
53+
54+
### Versions & Aliases
55+
56+
| Method | Path | Description | Request | Response |
57+
|--------|-------------------------------|--------------------------|--------------------------------|--------------------------------|
58+
| GET | `/:functionName/versions` | List versions | `?marker=string` | `{ versions: [...] }` |
59+
| GET | `/:functionName/aliases` | List aliases | `?marker=string` | `{ aliases: [...] }` |
60+
61+
### Request/Response Examples
62+
63+
**List functions:**
64+
65+
```bash
66+
curl http://localhost:3001/api/lambda
67+
```
68+
69+
```json
70+
{
71+
"functions": [
72+
{
73+
"functionName": "my-function",
74+
"functionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-function",
75+
"runtime": "nodejs20.x",
76+
"handler": "index.handler",
77+
"codeSize": 284,
78+
"lastModified": "2024-01-15T10:30:00.000+0000",
79+
"memorySize": 128,
80+
"timeout": 30
81+
}
82+
]
83+
}
84+
```
85+
86+
**Create function:**
87+
88+
```bash
89+
curl -X POST http://localhost:3001/api/lambda \
90+
-H "Content-Type: application/json" \
91+
-d '{
92+
"functionName": "my-function",
93+
"runtime": "nodejs20.x",
94+
"handler": "index.handler",
95+
"role": "arn:aws:iam::000000000000:role/lambda-role",
96+
"code": { "zipFile": "<base64-encoded-zip>" },
97+
"memorySize": 128,
98+
"timeout": 30
99+
}'
100+
```
101+
102+
```json
103+
{ "message": "Function 'my-function' created successfully" }
104+
```
105+
106+
**Invoke function:**
107+
108+
```bash
109+
curl -X POST http://localhost:3001/api/lambda/my-function/invoke \
110+
-H "Content-Type: application/json" \
111+
-d '{
112+
"payload": "{\"key\": \"value\"}",
113+
"invocationType": "RequestResponse"
114+
}'
115+
```
116+
117+
```json
118+
{
119+
"statusCode": 200,
120+
"payload": "{\"statusCode\":200,\"body\":\"hello\"}",
121+
"logResult": "START RequestId: ...\nEND RequestId: ...\n"
122+
}
123+
```
124+
125+
**Get function detail:**
126+
127+
```bash
128+
curl http://localhost:3001/api/lambda/my-function
129+
```
130+
131+
```json
132+
{
133+
"functionName": "my-function",
134+
"functionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-function",
135+
"runtime": "nodejs20.x",
136+
"handler": "index.handler",
137+
"role": "arn:aws:iam::000000000000:role/lambda-role",
138+
"codeSize": 284,
139+
"description": "My Lambda function",
140+
"timeout": 30,
141+
"memorySize": 128,
142+
"lastModified": "2024-01-15T10:30:00.000+0000",
143+
"codeSha256": "abc123...",
144+
"version": "$LATEST",
145+
"environment": { "NODE_ENV": "production" },
146+
"architectures": ["x86_64"],
147+
"layers": [],
148+
"packageType": "Zip"
149+
}
150+
```
151+
152+
**List triggers:**
153+
154+
```bash
155+
curl http://localhost:3001/api/lambda/my-function/triggers
156+
```
157+
158+
```json
159+
{
160+
"eventSourceMappings": [
161+
{
162+
"uuid": "abc-123",
163+
"eventSourceArn": "arn:aws:sqs:us-east-1:000000000000:my-queue",
164+
"state": "Enabled",
165+
"batchSize": 10
166+
}
167+
],
168+
"policyTriggers": [
169+
{
170+
"sid": "AllowS3Invoke",
171+
"service": "s3.amazonaws.com",
172+
"sourceArn": "arn:aws:s3:::my-bucket"
173+
}
174+
]
175+
}
176+
```
177+
178+
**Create event source mapping:**
179+
180+
```bash
181+
curl -X POST http://localhost:3001/api/lambda/my-function/event-source-mappings \
182+
-H "Content-Type: application/json" \
183+
-d '{
184+
"eventSourceArn": "arn:aws:sqs:us-east-1:000000000000:my-queue",
185+
"batchSize": 10,
186+
"enabled": true
187+
}'
188+
```
189+
190+
```json
191+
{ "message": "Event source mapping created successfully", "uuid": "abc-123" }
192+
```
193+
194+
**Delete function:**
195+
196+
```bash
197+
curl -X DELETE http://localhost:3001/api/lambda/my-function
198+
```
199+
200+
```json
201+
{ "success": true }
202+
```
203+
204+
## Error Handling
205+
206+
| Error | HTTP Status | Code |
207+
|---------------------------------|-------------|----------------------|
208+
| Function not found | 404 | `FUNCTION_NOT_FOUND` |
209+
| Function already exists / in use | 409 | `FUNCTION_CONFLICT` |
210+
| Event source mapping not found | 404 | `EVENT_SOURCE_MAPPING_NOT_FOUND` |
211+
| Invalid parameter value | 400 | `INVALID_PARAMETER` |
212+
| Rate limit exceeded | 429 | `TOO_MANY_REQUESTS` |
213+
| AWS service error | 502 | `SERVICE_ERROR` |
214+
215+
## UI Components
216+
217+
### Function List (`/lambda`)
218+
219+
- Searchable table with columns: Name, Runtime, Memory, Timeout, Last Modified
220+
- Function name links to detail view
221+
- Create button opens a dialog with fields for name, runtime, handler, role, memory, timeout, and optional zip upload
222+
- Per-row delete button with confirmation dialog
223+
224+
### Function Detail (`/lambda/:functionName`)
225+
226+
Five tabs:
227+
228+
1. **Configuration** — attribute grid (runtime, handler, role, memory, timeout, code size, state, package type, architectures, SHA256) and environment variables table
229+
2. **Invoke** — JSON payload textarea, invocation type selector (RequestResponse, Event, DryRun), result panel with status code, payload, error, and decoded log output
230+
3. **Triggers** — two sections:
231+
- **Resource-Based Policy Triggers** — read-only table showing services (S3, SNS, API Gateway, etc.) authorized to invoke the function, with source ARN and policy statement ID. Detected automatically from the function's resource-based policy.
232+
- **Event Source Mappings** — table of SQS/DynamoDB Streams/Kinesis mappings with state, batch size, and last modified. Supports creating new mappings (event source ARN + batch size) and deleting existing ones with confirmation dialog.
233+
4. **Versions** — table of published versions with version number, ARN, runtime, and last modified date
234+
5. **Aliases** — table of aliases with name, ARN, function version, and description
235+
236+
## Backend Architecture
237+
238+
The Lambda plugin follows the standard service plugin pattern:
239+
240+
```
241+
packages/backend/src/plugins/lambda/
242+
├── index.ts # Plugin registration (5 lines)
243+
├── routes.ts # 12 Fastify routes
244+
├── service.ts # LambdaService class wrapping @aws-sdk/client-lambda
245+
└── schemas.ts # TypeBox request/response schemas
246+
```
247+
248+
The `LambdaService` class maps AWS SDK exceptions to `AppError` instances with appropriate HTTP status codes via a centralized `mapLambdaError` function.

packages/backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@aws-sdk/client-dynamodb": "^3.1018.0",
1616
"@aws-sdk/client-dynamodb-streams": "^3.1018.0",
1717
"@aws-sdk/client-iam": "^3.1018.0",
18+
"@aws-sdk/client-lambda": "^3.1018.0",
1819
"@aws-sdk/client-s3": "^3.1018.0",
1920
"@aws-sdk/client-sns": "^3.1018.0",
2021
"@aws-sdk/client-sqs": "^3.1018.0",

packages/backend/src/aws/client-cache.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { CloudFormationClient } from "@aws-sdk/client-cloudformation";
22
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
33
import { DynamoDBStreamsClient } from "@aws-sdk/client-dynamodb-streams";
44
import { IAMClient } from "@aws-sdk/client-iam";
5+
import { LambdaClient } from "@aws-sdk/client-lambda";
56
import { S3Client } from "@aws-sdk/client-s3";
67
import { SNSClient } from "@aws-sdk/client-sns";
78
import { SQSClient } from "@aws-sdk/client-sqs";
@@ -14,6 +15,7 @@ export interface AwsClients {
1415
iam: IAMClient;
1516
cloudformation: CloudFormationClient;
1617
dynamodb: DynamoDBClient;
18+
lambda: LambdaClient;
1719
dynamodbDocument: DynamoDBDocumentClient;
1820
dynamodbStreams: DynamoDBStreamsClient;
1921
}
@@ -46,6 +48,7 @@ export class ClientCache {
4648
iam: new IAMClient(commonConfig),
4749
cloudformation: new CloudFormationClient(commonConfig),
4850
dynamodb,
51+
lambda: new LambdaClient(commonConfig),
4952
dynamodbDocument: DynamoDBDocumentClient.from(dynamodb, {
5053
marshallOptions: { removeUndefinedValues: true },
5154
}),

packages/backend/src/bundle.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import clientCachePlugin from "./plugins/client-cache.js";
99
import cloudformationPlugin from "./plugins/cloudformation/index.js";
1010
import dynamodbPlugin from "./plugins/dynamodb/index.js";
1111
import iamPlugin from "./plugins/iam/index.js";
12+
import lambdaPlugin from "./plugins/lambda/index.js";
1213
import localstackConfigPlugin from "./plugins/localstack-config.js";
1314
// Explicit plugin imports (replaces autoload for bundled builds)
1415
import s3Plugin from "./plugins/s3/index.js";
@@ -29,6 +30,7 @@ const pluginMap: Record<
2930
sqs: sqsPlugin,
3031
sns: snsPlugin,
3132
iam: iamPlugin,
33+
lambda: lambdaPlugin,
3234
cloudformation: cloudformationPlugin,
3335
dynamodb: dynamodbPlugin,
3436
};
@@ -50,7 +52,7 @@ async function main() {
5052
// Register localstack config plugin (decorates request with localstackConfig)
5153
await app.register(localstackConfigPlugin);
5254

53-
// Register client cache plugin (decorates instance with clientCache)
55+
// Register client cache plugin (decorates instance with clientCache)ho
5456
await app.register(clientCachePlugin);
5557

5658
// Health check

0 commit comments

Comments
 (0)