Skip to content

Commit a88de47

Browse files
committed
add sam template
1 parent 8740d70 commit a88de47

File tree

6 files changed

+542
-11
lines changed

6 files changed

+542
-11
lines changed

Examples/ServiceLifecycle/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ xcuserdata/
55
DerivedData/
66
.swiftpm/configuration/registries.json
77
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8-
.netrc
8+
.netrc
9+
.amazonq

Examples/ServiceLifecycle/README.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# ServiceLifecycle Lambda with PostgreSQL
2+
3+
This example demonstrates a Swift Lambda function that uses ServiceLifecycle to manage a PostgreSQL connection. The function connects to a publicly accessible RDS PostgreSQL database and queries user data.
4+
5+
## Architecture
6+
7+
- **Swift Lambda Function**: Uses ServiceLifecycle to manage PostgreSQL client lifecycle
8+
- **PostgreSQL RDS**: Publicly accessible database instance
9+
- **API Gateway**: HTTP endpoint to invoke the Lambda function
10+
- **VPC**: Custom VPC with public subnets for RDS and Lambda
11+
12+
## Prerequisites
13+
14+
- Swift 6.x toolchain
15+
- Docker (for building Lambda functions)
16+
- AWS CLI configured with appropriate permissions
17+
- SAM CLI installed
18+
19+
## Database Schema
20+
21+
The Lambda function expects a `users` table with the following structure:
22+
23+
```sql
24+
CREATE TABLE users (
25+
id SERIAL PRIMARY KEY,
26+
username VARCHAR(50) NOT NULL
27+
);
28+
29+
-- Insert some sample data
30+
INSERT INTO users (username) VALUES ('alice'), ('bob'), ('charlie');
31+
```
32+
33+
## Deployment
34+
35+
### Option 1: Using the deployment script
36+
37+
```bash
38+
./deploy.sh
39+
```
40+
41+
### Option 2: Manual deployment
42+
43+
1. **Build the Lambda function:**
44+
```bash
45+
swift package archive --allow-network-connections docker
46+
```
47+
48+
2. **Deploy with SAM:**
49+
```bash
50+
sam deploy
51+
```
52+
53+
### Option 3: Deploy with custom parameters
54+
55+
```bash
56+
sam deploy --parameter-overrides \
57+
DBUsername=myuser \
58+
DBPassword=MySecurePassword123! \
59+
DBName=mydatabase
60+
```
61+
62+
## Getting Connection Details
63+
64+
After deployment, get the database connection details:
65+
66+
```bash
67+
aws cloudformation describe-stacks \
68+
--stack-name servicelifecycle-stack \
69+
--query 'Stacks[0].Outputs'
70+
```
71+
72+
The output will include:
73+
- **DatabaseEndpoint**: Hostname to connect to
74+
- **DatabasePort**: Port number (5432)
75+
- **DatabaseName**: Database name
76+
- **DatabaseUsername**: Username
77+
- **DatabasePassword**: Password
78+
- **DatabaseConnectionString**: Complete connection string
79+
80+
## Connecting to the Database
81+
82+
### Using psql
83+
84+
```bash
85+
# Get the connection details from CloudFormation outputs
86+
DB_HOST=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabaseEndpoint`].OutputValue' --output text)
87+
DB_USER=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabaseUsername`].OutputValue' --output text)
88+
DB_NAME=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabaseName`].OutputValue' --output text)
89+
DB_PASSWORD=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabasePassword`].OutputValue' --output text)
90+
91+
# Connect with psql
92+
psql -h $DB_HOST -U $DB_USER -d $DB_NAME
93+
```
94+
95+
### Using connection string
96+
97+
```bash
98+
# Get the complete connection string
99+
CONNECTION_STRING=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`DatabaseConnectionString`].OutputValue' --output text)
100+
101+
# Connect with psql
102+
psql "$CONNECTION_STRING"
103+
```
104+
105+
## Setting up the Database
106+
107+
Once connected to the database, create the required table and sample data:
108+
109+
```sql
110+
-- Create the users table
111+
CREATE TABLE users (
112+
id SERIAL PRIMARY KEY,
113+
username VARCHAR(50) NOT NULL
114+
);
115+
116+
-- Insert sample data
117+
INSERT INTO users (username) VALUES
118+
('alice'),
119+
('bob'),
120+
('charlie'),
121+
('diana'),
122+
('eve');
123+
```
124+
125+
## Testing the Lambda Function
126+
127+
Get the API Gateway endpoint and test the function:
128+
129+
```bash
130+
# Get the API endpoint
131+
API_ENDPOINT=$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==`APIGatewayEndpoint`].OutputValue' --output text)
132+
133+
# Test the function
134+
curl "$API_ENDPOINT"
135+
```
136+
137+
The function will:
138+
1. Connect to the PostgreSQL database
139+
2. Query the `users` table
140+
3. Log the results
141+
4. Return "Done"
142+
143+
## Monitoring
144+
145+
Check the Lambda function logs:
146+
147+
```bash
148+
sam logs -n ServiceLifecycleLambda --stack-name servicelifecycle-stack --tail
149+
```
150+
151+
## Security Considerations
152+
153+
⚠️ **Important**: This example creates a publicly accessible PostgreSQL database for demonstration purposes. In production:
154+
155+
1. **Use private subnets** and VPC endpoints
156+
2. **Implement proper authentication** (IAM database authentication)
157+
3. **Use AWS Secrets Manager** for password management
158+
4. **Enable encryption** at rest and in transit
159+
5. **Configure proper security groups** with minimal required access
160+
6. **Enable database logging** and monitoring
161+
162+
## Cost Optimization
163+
164+
The template uses:
165+
- `db.t3.micro` instance (eligible for free tier)
166+
- Minimal storage allocation (20GB)
167+
- No Multi-AZ deployment
168+
- No automated backups
169+
170+
For production workloads, adjust these settings based on your requirements.
171+
172+
## Cleanup
173+
174+
To delete all resources:
175+
176+
```bash
177+
sam delete --stack-name servicelifecycle-stack
178+
```
179+
180+
## Troubleshooting
181+
182+
### Lambda can't connect to database
183+
184+
1. Check security groups allow traffic on port 5432
185+
2. Verify the database is publicly accessible
186+
3. Check VPC configuration and routing
187+
4. Verify database credentials in environment variables
188+
189+
### Database connection timeout
190+
191+
The PostgreSQL client may hang if the database is unreachable. This is a known issue with PostgresNIO. Ensure:
192+
1. Database is running and accessible
193+
2. Security groups are properly configured
194+
3. Network connectivity is available
195+
196+
### Build failures
197+
198+
Ensure you have:
199+
1. Swift 6.x toolchain installed
200+
2. Docker running
201+
3. Proper network connectivity for downloading dependencies
202+
203+
## Files
204+
205+
- `template.yaml`: SAM template defining all AWS resources
206+
- `samconfig.toml`: SAM configuration file
207+
- `deploy.sh`: Deployment script
208+
- `Sources/Lambda.swift`: Swift Lambda function code
209+
- `Sources/RootRDSCert.swift`: RDS root certificate for SSL connections
210+
- `Package.swift`: Swift package definition

Examples/ServiceLifecycle/Sources/Lambda.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,33 +21,34 @@ import ServiceLifecycle
2121
struct LambdaFunction {
2222

2323
static func main() async throws {
24-
LambdaFunction().main()
24+
try await LambdaFunction().main()
2525
}
2626

2727
private let pgClient: PostgresClient
2828
private var logger: Logger
2929
private init() throws {
3030
self.logger = Logger(label: "ServiceLifecycleExample")
31-
logger.logLevel = .trace
3231

33-
self.pgClient = try preparePostgresClient(
32+
self.pgClient = try LambdaFunction.preparePostgresClient(
3433
host: Lambda.env("DB_HOST") ?? "localhost",
3534
user: Lambda.env("DB_USER") ?? "postgres",
3635
password: Lambda.env("DB_PASSWORD") ?? "secret",
3736
dbName: Lambda.env("DB_NAME") ?? "test"
3837
)
3938
}
40-
private func main() {
39+
private func main() async throws {
4140

42-
/// Instantiate LambdaRuntime with a closure handler implementing the business logic of the Lambda function
43-
let runtime = LambdaRuntime(logger: logger, body: handler)
41+
// Instantiate LambdaRuntime with a handler implementing the business logic of the Lambda function
42+
// ok when https://github.com/swift-server/swift-aws-lambda-runtime/pull/523 will be merged
43+
//let runtime = LambdaRuntime(logger: logger, body: handler)
44+
let runtime = LambdaRuntime(body: handler)
4445

4546
/// Use ServiceLifecycle to manage the initialization and termination
4647
/// of the PGClient together with the LambdaRuntime
4748
let serviceGroup = ServiceGroup(
4849
services: [pgClient, runtime],
49-
gracefulShutdownSignals: [.sigterm, .sigint], // add SIGINT for CTRL+C in local testing
50-
// cancellationSignals: [.sigint],
50+
// gracefulShutdownSignals: [.sigterm, .sigint], // add SIGINT for CTRL+C in local testing
51+
cancellationSignals: [.sigint],
5152
logger: logger
5253
)
5354
try await serviceGroup.run()
@@ -56,7 +57,7 @@ struct LambdaFunction {
5657

5758
}
5859

59-
private func handler(event: String, context: LambdaContext) -> String {
60+
private func handler(event: String, context: LambdaContext) async -> String {
6061
do {
6162
// Use initialized service within the handler
6263
// IMPORTANT - CURRENTLY WHEN THERE IS AN ERROR, THIS CALL HANGS WHEN DB IS NOT REACHABLE
@@ -68,9 +69,10 @@ struct LambdaFunction {
6869
} catch {
6970
logger.error("PG Error: \(error)")
7071
}
72+
return "Done"
7173
}
7274

73-
private func preparePostgresClient(
75+
private static func preparePostgresClient(
7476
host: String,
7577
user: String,
7678
password: String,

Examples/ServiceLifecycle/deploy.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
# ServiceLifecycle Lambda Deployment Script
4+
set -e
5+
6+
echo "🚀 Building and deploying ServiceLifecycle Lambda with PostgreSQL..."
7+
8+
# Build the Lambda function
9+
echo "📦 Building Swift Lambda function..."
10+
swift package --disable-sandbox archive --allow-network-connections docker
11+
12+
# Deploy with SAM
13+
echo "🌩️ Deploying with SAM..."
14+
sam deploy
15+
16+
echo "✅ Deployment complete!"
17+
echo ""
18+
echo "📋 To get the database connection details, run:"
19+
echo "aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs'"
20+
echo ""
21+
echo "🧪 To test the Lambda function:"
22+
echo "curl \$(aws cloudformation describe-stacks --stack-name servicelifecycle-stack --query 'Stacks[0].Outputs[?OutputKey==\`APIGatewayEndpoint\`].OutputValue' --output text)"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# SAM configuration file for ServiceLifecycle example
2+
version = 0.1
3+
4+
[default.global.parameters]
5+
stack_name = "servicelifecycle-stack"
6+
7+
[default.build.parameters]
8+
cached = true
9+
parallel = true
10+
11+
[default.deploy.parameters]
12+
capabilities = "CAPABILITY_IAM"
13+
confirm_changeset = true
14+
resolve_s3 = true
15+
s3_prefix = "servicelifecycle"
16+
region = "us-east-1"
17+
image_repositories = []
18+
19+
[default.package.parameters]
20+
resolve_s3 = true
21+
22+
[default.sync.parameters]
23+
watch = true
24+
25+
[default.local_start_api.parameters]
26+
warm_containers = "EAGER"
27+
28+
[default.local_start_lambda.parameters]
29+
warm_containers = "EAGER"

0 commit comments

Comments
 (0)