Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ LocalStack Explorer provides an AWS Console-like experience for your local devel
| SQS | Fully implemented | Queue management, message operations, queue attributes, purge |
| SNS | Fully implemented | Topics, subscriptions, publish, tags, filter policies |
| IAM | Fully implemented | Users, groups, managed/inline policies, access keys, versioning |
| Lambda | Fully implemented | Functions CRUD, invoke, triggers, code/config update, versions, aliases |
| CloudFormation | Fully implemented | Stack CRUD, update, template editor, events, cross-service links |
| DynamoDB | Fully implemented | Table management, create, list, detail views |

## Quick Start

### Prerequisites

- **Node.js** >= 20
- **Node.js** >= 24 (see `.nvmrc`)
- **pnpm** >= 9
- **Docker** (for LocalStack)

Expand All @@ -37,7 +38,7 @@ pnpm install
docker compose up -d
```

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

### Development

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

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

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

# Enable all available services
ENABLED_SERVICES=s3,sqs,sns,iam,cloudformation,dynamodb
ENABLED_SERVICES=s3,sqs,sns,iam,lambda,cloudformation,dynamodb
```

Available service names: `s3`, `sqs`, `sns`, `iam`, `cloudformation`, `dynamodb`.
Available service names: `s3`, `sqs`, `sns`, `iam`, `lambda`, `cloudformation`, `dynamodb`.

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

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

### Active Service Detection

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.

## Project Structure

```
Expand All @@ -162,7 +167,8 @@ localstack-explorer/
├── packages/
│ ├── backend/ # Fastify API server
│ │ └── src/
│ │ ├── index.ts # Entry point (autoload plugins, serves frontend)
│ │ ├── index.ts # App factory (autoload plugins, serves frontend)
│ │ ├── server.ts # Server entry point (starts listening)
│ │ ├── bundle.ts # Bundle entry point (explicit plugin imports)
│ │ ├── config.ts # env-schema configuration
│ │ ├── health.ts # LocalStack connectivity check
Expand All @@ -172,6 +178,7 @@ localstack-explorer/
│ │ │ ├── sqs/ # Complete implementation
│ │ │ ├── sns/ # Complete implementation
│ │ │ ├── iam/ # Complete implementation
│ │ │ ├── lambda/ # Complete implementation
│ │ │ ├── cloudformation/ # Complete implementation
│ │ │ └── dynamodb/ # Complete implementation
│ │ └── shared/ # Error handling, shared types
Expand Down Expand Up @@ -219,6 +226,7 @@ localstack-explorer/
- **[SQS Service Guide](docs/sqs.md)** — Complete reference for SQS operations: queue management, message send/receive/delete, queue attributes, and purge.
- **[SNS Service Guide](docs/sns.md)** — Complete reference for SNS operations: topics, subscriptions, publish (single/batch), filter policies, and tags.
- **[IAM Service Guide](docs/iam.md)** — Complete reference for IAM operations: users, groups, managed/inline policies, access keys, policy versioning, and cascading deletes.
- **[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.
- **[CloudFormation Service Guide](docs/cloudformation.md)** — Complete reference for CloudFormation operations: stack CRUD, update, template editor, events timeline, and cross-service resource navigation.
- **[DynamoDB Service Guide](docs/dynamodb.md)** — Complete reference for DynamoDB operations: table management, creation, listing, and detail views.
- **[Adding New Services](docs/adding-new-services.md)** — Step-by-step guide to implement a new AWS service following the established plugin pattern.
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ services:
ports:
- "4566:4566"
environment:
- SERVICES=${ENABLED_SERVICES:-s3,sqs,sns,iam,cloudformation,dynamodb}
- SERVICES=${ENABLED_SERVICES:-s3,sqs,sns,iam,lambda,cloudformation,dynamodb}
- DEBUG=0
- EAGER_SERVICE_LOADING=1
volumes:
Expand Down
248 changes: 248 additions & 0 deletions docs/lambda.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# Lambda Service Guide

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.

## Features

- List, create, and delete Lambda functions
- View function details (configuration, environment variables, layers, architectures)
- Update function code (zip upload or S3 reference)
- Update function configuration (handler, runtime, memory, timeout, environment, role)
- Invoke functions synchronously with JSON payload
- View invocation results: status code, response payload, function errors, and decoded CloudWatch logs
- **Triggers**: view all trigger sources for a function
- **Resource-based policy triggers** (S3, SNS, API Gateway, EventBridge, etc.) — detected from the function's resource policy via `GetPolicy`
- **Event source mappings** (SQS, DynamoDB Streams, Kinesis, etc.) — with create and delete support
- Browse function versions
- Browse function aliases
- Search/filter functions by name
- Active service detection: Lambda appears greyed out in the UI when not running on LocalStack

## API Endpoints

All endpoints are prefixed with `/api/lambda`.

### Functions

| Method | Path | Description | Request | Response |
|--------|-------------------------------|--------------------------|--------------------------------|--------------------------------|
| GET | `/` | List functions | `?marker=string` | `{ functions: [...] }` |
| POST | `/` | Create function | `CreateFunctionBody` | `{ message: string }` |
| GET | `/:functionName` | Get function detail | -- | `FunctionDetail` |
| PUT | `/:functionName/code` | Update function code | `{ zipFile?, s3Bucket?, s3Key? }` | `{ message: string }` |
| PUT | `/:functionName/config` | Update function config | `UpdateFunctionConfigBody` | `{ message: string }` |
| DELETE | `/:functionName` | Delete function | -- | `{ success: boolean }` |

### Invocation

| Method | Path | Description | Request | Response |
|--------|-------------------------------|--------------------------|--------------------------------|--------------------------------|
| POST | `/:functionName/invoke` | Invoke function | `{ payload?, invocationType? }` | `InvokeFunctionResponse` |

### Triggers

| Method | Path | Description | Request | Response |
|--------|--------------------------------------------|--------------------------------|--------------------------------------------------|-------------------------------------------|
| GET | `/:functionName/triggers` | List all triggers | `?marker=string` | `{ eventSourceMappings, policyTriggers }` |
| POST | `/:functionName/event-source-mappings` | Create event source mapping | `{ eventSourceArn, batchSize?, enabled?, ... }` | `{ message, uuid }` |
| DELETE | `/event-source-mappings/:uuid` | Delete event source mapping | -- | `{ success: boolean }` |

The `GET /:functionName/triggers` endpoint combines two data sources:
- **Event source mappings** — Lambda's `ListEventSourceMappings` (SQS queues, DynamoDB Streams, Kinesis streams)
- **Policy triggers** — parsed from the function's resource-based policy via `GetPolicy` (S3 bucket notifications, SNS topics, API Gateway, EventBridge rules, etc.)

### Versions & Aliases

| Method | Path | Description | Request | Response |
|--------|-------------------------------|--------------------------|--------------------------------|--------------------------------|
| GET | `/:functionName/versions` | List versions | `?marker=string` | `{ versions: [...] }` |
| GET | `/:functionName/aliases` | List aliases | `?marker=string` | `{ aliases: [...] }` |

### Request/Response Examples

**List functions:**

```bash
curl http://localhost:3001/api/lambda
```

```json
{
"functions": [
{
"functionName": "my-function",
"functionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-function",
"runtime": "nodejs20.x",
"handler": "index.handler",
"codeSize": 284,
"lastModified": "2024-01-15T10:30:00.000+0000",
"memorySize": 128,
"timeout": 30
}
]
}
```

**Create function:**

```bash
curl -X POST http://localhost:3001/api/lambda \
-H "Content-Type: application/json" \
-d '{
"functionName": "my-function",
"runtime": "nodejs20.x",
"handler": "index.handler",
"role": "arn:aws:iam::000000000000:role/lambda-role",
"code": { "zipFile": "<base64-encoded-zip>" },
"memorySize": 128,
"timeout": 30
}'
```

```json
{ "message": "Function 'my-function' created successfully" }
```

**Invoke function:**

```bash
curl -X POST http://localhost:3001/api/lambda/my-function/invoke \
-H "Content-Type: application/json" \
-d '{
"payload": "{\"key\": \"value\"}",
"invocationType": "RequestResponse"
}'
```

```json
{
"statusCode": 200,
"payload": "{\"statusCode\":200,\"body\":\"hello\"}",
"logResult": "START RequestId: ...\nEND RequestId: ...\n"
}
```

**Get function detail:**

```bash
curl http://localhost:3001/api/lambda/my-function
```

```json
{
"functionName": "my-function",
"functionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-function",
"runtime": "nodejs20.x",
"handler": "index.handler",
"role": "arn:aws:iam::000000000000:role/lambda-role",
"codeSize": 284,
"description": "My Lambda function",
"timeout": 30,
"memorySize": 128,
"lastModified": "2024-01-15T10:30:00.000+0000",
"codeSha256": "abc123...",
"version": "$LATEST",
"environment": { "NODE_ENV": "production" },
"architectures": ["x86_64"],
"layers": [],
"packageType": "Zip"
}
```

**List triggers:**

```bash
curl http://localhost:3001/api/lambda/my-function/triggers
```

```json
{
"eventSourceMappings": [
{
"uuid": "abc-123",
"eventSourceArn": "arn:aws:sqs:us-east-1:000000000000:my-queue",
"state": "Enabled",
"batchSize": 10
}
],
"policyTriggers": [
{
"sid": "AllowS3Invoke",
"service": "s3.amazonaws.com",
"sourceArn": "arn:aws:s3:::my-bucket"
}
]
}
```

**Create event source mapping:**

```bash
curl -X POST http://localhost:3001/api/lambda/my-function/event-source-mappings \
-H "Content-Type: application/json" \
-d '{
"eventSourceArn": "arn:aws:sqs:us-east-1:000000000000:my-queue",
"batchSize": 10,
"enabled": true
}'
```

```json
{ "message": "Event source mapping created successfully", "uuid": "abc-123" }
```

**Delete function:**

```bash
curl -X DELETE http://localhost:3001/api/lambda/my-function
```

```json
{ "success": true }
```

## Error Handling

| Error | HTTP Status | Code |
|---------------------------------|-------------|----------------------|
| Function not found | 404 | `FUNCTION_NOT_FOUND` |
| Function already exists / in use | 409 | `FUNCTION_CONFLICT` |
| Event source mapping not found | 404 | `EVENT_SOURCE_MAPPING_NOT_FOUND` |
| Invalid parameter value | 400 | `INVALID_PARAMETER` |
| Rate limit exceeded | 429 | `TOO_MANY_REQUESTS` |
| AWS service error | 502 | `SERVICE_ERROR` |

## UI Components

### Function List (`/lambda`)

- Searchable table with columns: Name, Runtime, Memory, Timeout, Last Modified
- Function name links to detail view
- Create button opens a dialog with fields for name, runtime, handler, role, memory, timeout, and optional zip upload
- Per-row delete button with confirmation dialog

### Function Detail (`/lambda/:functionName`)

Five tabs:

1. **Configuration** — attribute grid (runtime, handler, role, memory, timeout, code size, state, package type, architectures, SHA256) and environment variables table
2. **Invoke** — JSON payload textarea, invocation type selector (RequestResponse, Event, DryRun), result panel with status code, payload, error, and decoded log output
3. **Triggers** — two sections:
- **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.
- **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.
4. **Versions** — table of published versions with version number, ARN, runtime, and last modified date
5. **Aliases** — table of aliases with name, ARN, function version, and description

## Backend Architecture

The Lambda plugin follows the standard service plugin pattern:

```
packages/backend/src/plugins/lambda/
├── index.ts # Plugin registration (5 lines)
├── routes.ts # 12 Fastify routes
├── service.ts # LambdaService class wrapping @aws-sdk/client-lambda
└── schemas.ts # TypeBox request/response schemas
```

The `LambdaService` class maps AWS SDK exceptions to `AppError` instances with appropriate HTTP status codes via a centralized `mapLambdaError` function.
1 change: 1 addition & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@aws-sdk/client-dynamodb": "^3.1018.0",
"@aws-sdk/client-dynamodb-streams": "^3.1018.0",
"@aws-sdk/client-iam": "^3.1018.0",
"@aws-sdk/client-lambda": "^3.1018.0",
"@aws-sdk/client-s3": "^3.1018.0",
"@aws-sdk/client-sns": "^3.1018.0",
"@aws-sdk/client-sqs": "^3.1018.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/aws/client-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CloudFormationClient } from "@aws-sdk/client-cloudformation";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBStreamsClient } from "@aws-sdk/client-dynamodb-streams";
import { IAMClient } from "@aws-sdk/client-iam";
import { LambdaClient } from "@aws-sdk/client-lambda";
import { S3Client } from "@aws-sdk/client-s3";
import { SNSClient } from "@aws-sdk/client-sns";
import { SQSClient } from "@aws-sdk/client-sqs";
Expand All @@ -14,6 +15,7 @@ export interface AwsClients {
iam: IAMClient;
cloudformation: CloudFormationClient;
dynamodb: DynamoDBClient;
lambda: LambdaClient;
dynamodbDocument: DynamoDBDocumentClient;
dynamodbStreams: DynamoDBStreamsClient;
}
Expand Down Expand Up @@ -46,6 +48,7 @@ export class ClientCache {
iam: new IAMClient(commonConfig),
cloudformation: new CloudFormationClient(commonConfig),
dynamodb,
lambda: new LambdaClient(commonConfig),
dynamodbDocument: DynamoDBDocumentClient.from(dynamodb, {
marshallOptions: { removeUndefinedValues: true },
}),
Expand Down
4 changes: 3 additions & 1 deletion packages/backend/src/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import clientCachePlugin from "./plugins/client-cache.js";
import cloudformationPlugin from "./plugins/cloudformation/index.js";
import dynamodbPlugin from "./plugins/dynamodb/index.js";
import iamPlugin from "./plugins/iam/index.js";
import lambdaPlugin from "./plugins/lambda/index.js";
import localstackConfigPlugin from "./plugins/localstack-config.js";
// Explicit plugin imports (replaces autoload for bundled builds)
import s3Plugin from "./plugins/s3/index.js";
Expand All @@ -29,6 +30,7 @@ const pluginMap: Record<
sqs: sqsPlugin,
sns: snsPlugin,
iam: iamPlugin,
lambda: lambdaPlugin,
cloudformation: cloudformationPlugin,
dynamodb: dynamodbPlugin,
};
Expand All @@ -50,7 +52,7 @@ async function main() {
// Register localstack config plugin (decorates request with localstackConfig)
await app.register(localstackConfigPlugin);

// Register client cache plugin (decorates instance with clientCache)
// Register client cache plugin (decorates instance with clientCache)ho
await app.register(clientCachePlugin);

// Health check
Expand Down
Loading
Loading