Skip to content

Commit e6ba07f

Browse files
authored
[example] Add example for Swift Service Lifecycle (#522)
Now that task cancellation works, re publishing this PR with a new example for Swift Service Lifecycle
1 parent 36dadf9 commit e6ba07f

File tree

20 files changed

+1409
-9
lines changed

20 files changed

+1409
-9
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
# We pass the list of examples here, but we can't pass an array as argument
3737
# Instead, we pass a String with a valid JSON array.
3838
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
39-
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'StreamingFromEvent', 'Testing', 'Tutorial' ]"
39+
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'StreamingFromEvent', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
4040
archive_plugin_examples: "[ 'HelloWorld', 'ResourcesPackaging' ]"
4141
archive_plugin_enabled: true
4242

.licenseignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,5 @@ Package.resolved
3434
*.yml
3535
**/.npmignore
3636
**/*.json
37-
**/*.txt
37+
**/*.txt
38+
*.toml
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
9+
.amazonq
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Infrastructure Architecture
2+
3+
This document describes the AWS infrastructure deployed by the ServiceLifecycle example's SAM template.
4+
5+
## Overview
6+
7+
The infrastructure consists of a secure VPC setup with private subnets only, containing both the PostgreSQL RDS instance and Lambda function. The architecture is optimized for cost and security with complete network isolation.
8+
9+
## Network Architecture
10+
11+
### VPC Configuration
12+
- **VPC**: Custom VPC with CIDR block `10.0.0.0/16`
13+
- **DNS Support**: DNS hostnames and DNS resolution enabled
14+
15+
### Subnet Layout
16+
- **Private Subnets**:
17+
- Private Subnet 1: `10.0.3.0/24` (AZ 1)
18+
- Private Subnet 2: `10.0.4.0/24` (AZ 2)
19+
- Used for RDS PostgreSQL database and Lambda function
20+
- No public IP addresses assigned
21+
- Complete isolation from internet
22+
23+
### Network Components
24+
- **VPC-only architecture**: No internet connectivity required
25+
- **Route Tables**: Default VPC routing for internal communication
26+
27+
## Security Groups
28+
29+
### Lambda Security Group
30+
- **Outbound Rules**:
31+
- PostgreSQL (5432): Restricted to VPC CIDR `10.0.0.0/16`
32+
33+
### Database Security Group
34+
- **Inbound Rules**:
35+
- PostgreSQL (5432): Only allows connections from the Lambda Security Group
36+
37+
## Database Configuration
38+
39+
### PostgreSQL RDS Instance
40+
- **Instance Type**: `db.t3.micro` (cost-optimized)
41+
- **Engine**: PostgreSQL 15.7
42+
- **Storage**: 20GB GP2 (SSD)
43+
- **Network**: Deployed in private subnets with no public access
44+
- **Security**:
45+
- Storage encryption enabled
46+
- SSL/TLS connections supported
47+
- Credentials stored in AWS Secrets Manager
48+
- **High Availability**: Multi-AZ disabled (development configuration)
49+
- **Backup**: Automated backups disabled (development configuration)
50+
51+
### Database Subnet Group
52+
- Spans both private subnets for availability
53+
54+
## Lambda Function Configuration
55+
56+
### Service Lifecycle Lambda
57+
- **Runtime**: Custom runtime (provided.al2)
58+
- **Architecture**: ARM64
59+
- **Memory**: 512MB
60+
- **Timeout**: 60 seconds
61+
- **Network**: Deployed in private subnets with access to database within VPC
62+
- **Environment Variables**:
63+
- `LOG_LEVEL`: trace
64+
- `DB_HOST`: RDS endpoint address
65+
- `DB_USER`: Retrieved from Secrets Manager
66+
- `DB_PASSWORD`: Retrieved from Secrets Manager
67+
- `DB_NAME`: Database name from parameter
68+
69+
## API Gateway
70+
71+
- **Type**: HTTP API
72+
- **Integration**: Direct Lambda integration
73+
- **Authentication**: None (for demonstration purposes)
74+
75+
## Secrets Management
76+
77+
### Database Credentials
78+
- **Storage**: AWS Secrets Manager
79+
- **Secret Name**: `{StackName}-db-credentials`
80+
- **Content**:
81+
- Username: "postgres"
82+
- Password: Auto-generated 16-character password
83+
- Special characters excluded: `"@/\`
84+
85+
## SAM Outputs
86+
87+
The template provides several outputs to facilitate working with the deployed resources:
88+
89+
- **APIGatewayEndpoint**: URL to invoke the Lambda function
90+
- **DatabaseEndpoint**: Hostname for the PostgreSQL instance
91+
- **DatabasePort**: Port number for PostgreSQL (5432)
92+
- **DatabaseName**: Name of the created database
93+
- **DatabaseSecretArn**: ARN of the secret containing credentials
94+
- **DatabaseConnectionInstructions**: Instructions for retrieving connection details
95+
- **ConnectionDetails**: Consolidated connection information
96+
97+
## Security Considerations
98+
99+
This infrastructure implements several security best practices:
100+
101+
1. **Complete Network Isolation**: Both database and Lambda are in private subnets with no direct acces to or from the internet
102+
2. **Least Privilege**: Security groups restrict traffic to only necessary ports and sources
103+
3. **Encryption**: Database storage is encrypted at rest
104+
4. **Secure Credentials**: Database credentials are managed through AWS Secrets Manager
105+
5. **Secure Communication**: Lambda function connects to database over encrypted connections
106+
107+
## Cost Analysis
108+
109+
### Monthly Cost Breakdown (US East 1 Region)
110+
111+
#### Billable AWS Resources:
112+
113+
**1. RDS PostgreSQL Database**
114+
- Instance (db.t3.micro): $13.87/month (730 hours × $0.019/hour)
115+
- Storage (20GB GP2): $2.30/month (20GB × $0.115/GB/month)
116+
- Backup Storage: $0 (BackupRetentionPeriod: 0)
117+
- Multi-AZ: $0 (disabled)
118+
- **RDS Subtotal: $16.17/month**
119+
120+
**2. AWS Secrets Manager**
121+
- Secret Storage: $0.40/month per secret
122+
- API Calls: ~$0.05 per 10,000 calls (minimal for Lambda access)
123+
- **Secrets Manager Subtotal: ~$0.45/month**
124+
125+
**3. AWS Lambda**
126+
- Memory: 512MB ARM64
127+
- Free Tier: 1M requests + 400,000 GB-seconds/month
128+
- Development Usage: $0 (within free tier)
129+
- **Lambda Subtotal: $0/month**
130+
131+
**4. API Gateway (HTTP API)**
132+
- Free Tier: 1M requests/month
133+
- Development Usage: $0 (within free tier)
134+
- **API Gateway Subtotal: $0/month**
135+
136+
#### Free AWS Resources:
137+
- VPC, Private Subnets, Security Groups, DB Subnet Group: $0
138+
139+
### Total Monthly Cost:
140+
141+
| Service | Cost | Notes |
142+
|---------|------|---------|
143+
| RDS PostgreSQL | $16.17 | db.t3.micro + 20GB storage |
144+
| Secrets Manager | $0.45 | 1 secret + minimal API calls |
145+
| Lambda | $0.00 | Within free tier |
146+
| API Gateway | $0.00 | Within free tier |
147+
| VPC Components | $0.00 | No charges |
148+
| **TOTAL** | **$16.62/month** | |
149+
150+
### With RDS Free Tier (First 12 Months):
151+
- RDS Instance: $0 (750 hours/month free)
152+
- RDS Storage: $0 (20GB free)
153+
- **Total with Free Tier: ~$0.45/month**
154+
155+
### Production Scaling Estimates:
156+
- Higher Lambda usage: +$0.20 per million requests
157+
- More RDS storage: +$0.115 per additional GB/month
158+
- Multi-AZ RDS: ~2x RDS instance cost
159+
- Backup storage: $0.095/GB/month
160+
161+
This architecture provides maximum cost efficiency while maintaining security and functionality for development workloads.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// swift-tools-version: 6.0
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
// needed for CI to test the local version of the library
7+
import struct Foundation.URL
8+
9+
let package = Package(
10+
name: "LambdaWithServiceLifecycle",
11+
platforms: [
12+
.macOS(.v15)
13+
],
14+
dependencies: [
15+
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.26.0"),
16+
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"),
17+
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "1.0.0"),
18+
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.6.3"),
19+
],
20+
targets: [
21+
.executableTarget(
22+
name: "LambdaWithServiceLifecycle",
23+
dependencies: [
24+
.product(name: "PostgresNIO", package: "postgres-nio"),
25+
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
26+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
27+
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
28+
]
29+
)
30+
]
31+
)
32+
33+
if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
34+
localDepsPath != "",
35+
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
36+
v.isDirectory == true
37+
{
38+
// when we use the local runtime as deps, let's remove the dependency added above
39+
let indexToRemove = package.dependencies.firstIndex { dependency in
40+
if case .sourceControl(
41+
name: _,
42+
location: "https://github.com/swift-server/swift-aws-lambda-runtime.git",
43+
requirement: _
44+
) = dependency.kind {
45+
return true
46+
}
47+
return false
48+
}
49+
if let indexToRemove {
50+
package.dependencies.remove(at: indexToRemove)
51+
}
52+
53+
// then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..)
54+
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
55+
package.dependencies += [
56+
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
57+
]
58+
}

0 commit comments

Comments
 (0)