Skip to content

[example] Add example for Swift Service Lifecycle #522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c4c71cc
initial commit
sebsto Jan 5, 2025
9b0a617
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jan 8, 2025
0e374ab
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jun 30, 2025
54d2fd1
merge with recent changes
sebsto Jun 30, 2025
5c4d33b
remove unneeded import
sebsto Jun 30, 2025
0993cad
add example code
sebsto Jun 30, 2025
8740d70
update example
sebsto Jun 30, 2025
a88de47
add sam template
sebsto Jun 30, 2025
6d858c2
Merge branch 'swift-server:main' into sebsto/servicelifecycle
sebsto Jul 7, 2025
e26ae32
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jul 21, 2025
47eac09
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jul 21, 2025
06231ed
fix yam + script
sebsto Jul 21, 2025
52a7c8c
fix soundness
sebsto Jul 21, 2025
123bc6c
fix soundness
sebsto Jul 21, 2025
4679fe1
fix soundness
sebsto Jul 21, 2025
e35c84e
Add servicelifecycle to CI
sebsto Jul 21, 2025
1de4aca
fix licenseignore
sebsto Jul 21, 2025
9bbee05
fix syntax error in yaml
sebsto Jul 21, 2025
e4ad2e5
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jul 22, 2025
a5ad767
fix error on CTRL-C
sebsto Jul 22, 2025
554b196
update example
sebsto Jul 22, 2025
a0959b0
add readme and infrastructure doc
sebsto Jul 22, 2025
27cf38b
Merge branch 'sebsto/servicelifecycle' of github.com:sebsto/swift-aws…
sebsto Jul 22, 2025
7ee1786
fix yaml lint
sebsto Jul 22, 2025
2ce51ac
fix unused warnings
sebsto Jul 22, 2025
c661cbe
change DB name
sebsto Jul 22, 2025
d4b5ebe
automatically populate the table at first usage
sebsto Jul 22, 2025
e0bc6ea
improve logging
sebsto Jul 22, 2025
32b16bf
add comments
sebsto Jul 22, 2025
59052f3
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jul 23, 2025
1799248
simplify infrastructure architecture
sebsto Jul 23, 2025
951a6dc
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jul 23, 2025
018d9ce
add api gateway event as input and output
sebsto Jul 23, 2025
66ac850
add license header
sebsto Jul 23, 2025
add035c
remove the word hang
sebsto Jul 23, 2025
86ddb9e
swift format
sebsto Jul 23, 2025
de6c5ad
disable incorrect shellcheck warning
sebsto Jul 23, 2025
3f35fb1
another attemp to fix shelcheck
sebsto Jul 24, 2025
4f76421
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jul 24, 2025
f68526b
Merge branch 'main' into sebsto/servicelifecycle
sebsto Jul 25, 2025
a8bbe45
rename project + remove public VPC and NAT
sebsto Jul 25, 2025
b06e0df
fix CI
sebsto Jul 25, 2025
707f380
add a logging statement in case of error
sebsto Jul 25, 2025
a7aa99e
swift-format
sebsto Jul 25, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
# We pass the list of examples here, but we can't pass an array as argument
# Instead, we pass a String with a valid JSON array.
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'StreamingFromEvent', 'Testing', 'Tutorial' ]"
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'StreamingFromEvent', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
archive_plugin_examples: "[ 'HelloWorld', 'ResourcesPackaging' ]"
archive_plugin_enabled: true

Expand Down
3 changes: 2 additions & 1 deletion .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ Package.resolved
*.yml
**/.npmignore
**/*.json
**/*.txt
**/*.txt
*.toml
9 changes: 9 additions & 0 deletions Examples/ServiceLifecycle+Postgres/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.amazonq
161 changes: 161 additions & 0 deletions Examples/ServiceLifecycle+Postgres/INFRASTRUCTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Infrastructure Architecture

This document describes the AWS infrastructure deployed by the ServiceLifecycle example's SAM template.

## Overview

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.

## Network Architecture

### VPC Configuration
- **VPC**: Custom VPC with CIDR block `10.0.0.0/16`
- **DNS Support**: DNS hostnames and DNS resolution enabled

### Subnet Layout
- **Private Subnets**:
- Private Subnet 1: `10.0.3.0/24` (AZ 1)
- Private Subnet 2: `10.0.4.0/24` (AZ 2)
- Used for RDS PostgreSQL database and Lambda function
- No public IP addresses assigned
- Complete isolation from internet

### Network Components
- **VPC-only architecture**: No internet connectivity required
- **Route Tables**: Default VPC routing for internal communication

## Security Groups

### Lambda Security Group
- **Outbound Rules**:
- PostgreSQL (5432): Restricted to VPC CIDR `10.0.0.0/16`

### Database Security Group
- **Inbound Rules**:
- PostgreSQL (5432): Only allows connections from the Lambda Security Group

## Database Configuration

### PostgreSQL RDS Instance
- **Instance Type**: `db.t3.micro` (cost-optimized)
- **Engine**: PostgreSQL 15.7
- **Storage**: 20GB GP2 (SSD)
- **Network**: Deployed in private subnets with no public access
- **Security**:
- Storage encryption enabled
- SSL/TLS connections supported
- Credentials stored in AWS Secrets Manager
- **High Availability**: Multi-AZ disabled (development configuration)
- **Backup**: Automated backups disabled (development configuration)

### Database Subnet Group
- Spans both private subnets for availability

## Lambda Function Configuration

### Service Lifecycle Lambda
- **Runtime**: Custom runtime (provided.al2)
- **Architecture**: ARM64
- **Memory**: 512MB
- **Timeout**: 60 seconds
- **Network**: Deployed in private subnets with access to database within VPC
- **Environment Variables**:
- `LOG_LEVEL`: trace
- `DB_HOST`: RDS endpoint address
- `DB_USER`: Retrieved from Secrets Manager
- `DB_PASSWORD`: Retrieved from Secrets Manager
- `DB_NAME`: Database name from parameter

## API Gateway

- **Type**: HTTP API
- **Integration**: Direct Lambda integration
- **Authentication**: None (for demonstration purposes)

## Secrets Management

### Database Credentials
- **Storage**: AWS Secrets Manager
- **Secret Name**: `{StackName}-db-credentials`
- **Content**:
- Username: "postgres"
- Password: Auto-generated 16-character password
- Special characters excluded: `"@/\`

## SAM Outputs

The template provides several outputs to facilitate working with the deployed resources:

- **APIGatewayEndpoint**: URL to invoke the Lambda function
- **DatabaseEndpoint**: Hostname for the PostgreSQL instance
- **DatabasePort**: Port number for PostgreSQL (5432)
- **DatabaseName**: Name of the created database
- **DatabaseSecretArn**: ARN of the secret containing credentials
- **DatabaseConnectionInstructions**: Instructions for retrieving connection details
- **ConnectionDetails**: Consolidated connection information

## Security Considerations

This infrastructure implements several security best practices:

1. **Complete Network Isolation**: Both database and Lambda are in private subnets with no direct acces to or from the internet
2. **Least Privilege**: Security groups restrict traffic to only necessary ports and sources
3. **Encryption**: Database storage is encrypted at rest
4. **Secure Credentials**: Database credentials are managed through AWS Secrets Manager
5. **Secure Communication**: Lambda function connects to database over encrypted connections

## Cost Analysis

### Monthly Cost Breakdown (US East 1 Region)

#### Billable AWS Resources:

**1. RDS PostgreSQL Database**
- Instance (db.t3.micro): $13.87/month (730 hours × $0.019/hour)
- Storage (20GB GP2): $2.30/month (20GB × $0.115/GB/month)
- Backup Storage: $0 (BackupRetentionPeriod: 0)
- Multi-AZ: $0 (disabled)
- **RDS Subtotal: $16.17/month**

**2. AWS Secrets Manager**
- Secret Storage: $0.40/month per secret
- API Calls: ~$0.05 per 10,000 calls (minimal for Lambda access)
- **Secrets Manager Subtotal: ~$0.45/month**

**3. AWS Lambda**
- Memory: 512MB ARM64
- Free Tier: 1M requests + 400,000 GB-seconds/month
- Development Usage: $0 (within free tier)
- **Lambda Subtotal: $0/month**

**4. API Gateway (HTTP API)**
- Free Tier: 1M requests/month
- Development Usage: $0 (within free tier)
- **API Gateway Subtotal: $0/month**

#### Free AWS Resources:
- VPC, Private Subnets, Security Groups, DB Subnet Group: $0

### Total Monthly Cost:

| Service | Cost | Notes |
|---------|------|---------|
| RDS PostgreSQL | $16.17 | db.t3.micro + 20GB storage |
| Secrets Manager | $0.45 | 1 secret + minimal API calls |
| Lambda | $0.00 | Within free tier |
| API Gateway | $0.00 | Within free tier |
| VPC Components | $0.00 | No charges |
| **TOTAL** | **$16.62/month** | |

### With RDS Free Tier (First 12 Months):
- RDS Instance: $0 (750 hours/month free)
- RDS Storage: $0 (20GB free)
- **Total with Free Tier: ~$0.45/month**

### Production Scaling Estimates:
- Higher Lambda usage: +$0.20 per million requests
- More RDS storage: +$0.115 per additional GB/month
- Multi-AZ RDS: ~2x RDS instance cost
- Backup storage: $0.095/GB/month

This architecture provides maximum cost efficiency while maintaining security and functionality for development workloads.
58 changes: 58 additions & 0 deletions Examples/ServiceLifecycle+Postgres/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

// needed for CI to test the local version of the library
import struct Foundation.URL

let package = Package(
name: "LambdaWithServiceLifecycle",
platforms: [
.macOS(.v15)
],
dependencies: [
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.26.0"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "1.0.0"),
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.6.3"),
],
targets: [
.executableTarget(
name: "LambdaWithServiceLifecycle",
dependencies: [
.product(name: "PostgresNIO", package: "postgres-nio"),
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
]
)
]
)

if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
localDepsPath != "",
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
v.isDirectory == true
{
// when we use the local runtime as deps, let's remove the dependency added above
let indexToRemove = package.dependencies.firstIndex { dependency in
if case .sourceControl(
name: _,
location: "https://github.com/swift-server/swift-aws-lambda-runtime.git",
requirement: _
) = dependency.kind {
return true
}
return false
}
if let indexToRemove {
package.dependencies.remove(at: indexToRemove)
}

// then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..)
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
package.dependencies += [
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
]
}
Loading