Skip to content

Commit 42fe397

Browse files
committed
feat(eager-loading): implement eager service loading with health check improvements
Complete implementation of eager loading feature with critical bug fixes: **Feature Implementation:** - Add EagerLoadedServices property to LocalStackContainerOptions - Automatically set EAGER_SERVICE_LOADING and SERVICES environment variables - Update health check to verify eagerly loaded services are running - Add environment variable collision detection for SERVICES/EAGER_SERVICE_LOADING **Critical Bug Fixes:** - Fix missing resourceBuilder assignment on line 200 (fluent API pattern) - Fix case-insensitive service name lookup in health check - Fix URL construction to handle trailing slashes **Health Check Improvements:** - Use IHttpClientFactory with proper timeout configuration - Add case-insensitive JsonObject key matching (StringComparison.OrdinalIgnoreCase) - Implement IDisposable pattern for proper resource cleanup - Add specific error handling for HttpRequestException and timeouts **Testing:** - Add comprehensive LocalStackHealthCheck unit tests (9 test cases) - Add integration tests for eager loading scenarios - Add tests for environment variable collision detection - Add tests for multiple services, empty lists, and edge cases **Documentation:** - Add Container Configuration section to README - Create comprehensive CONFIGURATION.md guide - Document lazy vs eager loading patterns - Add configuration patterns for Dev/CI/CD/Testing - Cross-reference official LocalStack documentation - Add troubleshooting guide Co-authored-by: slang25 <[email protected]> Closes #7 Refs #8
1 parent 21b2ed1 commit 42fe397

File tree

14 files changed

+930
-169
lines changed

14 files changed

+930
-169
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525

2626
### Changed
2727

28-
- **LocalStack Container**: Updated from `4.6.0``4.9.1`
28+
- **LocalStack Container**: Updated from `4.6.0``4.9.2`
2929
- **Aspire.Hosting**: Updated to `9.5.0` (aligns with release version)
3030
- **Aspire.Hosting.AWS**: Updated to `9.2.6` (latest stable AWS integration)
3131
- **Aspire.Hosting.Testing**: Updated to `9.5.0`

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ dotnet add package LocalStack.Aspire.Hosting --prerelease --source github-locals
3333
3434
## Usage
3535

36+
> 💡 **Prefer learning by example?** Check out our [complete working examples](#examples) in the playground (CloudFormation, CDK, Lambda). Clone, run, explore - then come back here for configuration details.
37+
3638
When LocalStack is disabled in configuration, both host and client configurations automatically fall back to real AWS services without requiring code changes. The [LocalStack.NET Client](https://github.com/localstack-dotnet/localstack-dotnet-client) automatically switches to AWS's official client factory when LocalStack is not enabled.
3739

3840
### Host Configuration (AppHost)
@@ -76,6 +78,35 @@ The `UseLocalStack()` method automatically:
7678
- Sets up proper dependency ordering and CDK bootstrap if needed
7779
- Transfers LocalStack configuration to service projects via environment variables
7880

81+
#### Container Configuration
82+
83+
The `configureContainer` parameter allows you to customize LocalStack container behavior. By default, LocalStack uses lazy loading - services start only when first accessed. For faster startup in CI or when you know which services you need, configure eager loading:
84+
85+
```csharp
86+
builder.AddLocalStack(configureContainer: container =>
87+
{
88+
// Eagerly load specific services for faster startup
89+
container.EagerLoadedServices = [AwsService.Sqs, AwsService.DynamoDB, AwsService.S3];
90+
91+
// Recommended: Clean up container when application stops
92+
container.Lifetime = ContainerLifetime.Session;
93+
94+
// Optional: Enable verbose logging for troubleshooting
95+
container.DebugLevel = 1;
96+
container.LogLevel = LocalStackLogLevel.Debug;
97+
});
98+
```
99+
100+
**Available Options:**
101+
102+
- **`EagerLoadedServices`** - Pre-load specific AWS services at startup (reduces cold start latency)
103+
- **`Lifetime`** - Container lifecycle: `Persistent` (survives restarts) or `Session` (cleaned up on stop)
104+
- **`DebugLevel`** - LocalStack debug verbosity (0 = default, 1 = verbose)
105+
- **`LogLevel`** - Log level control (Error, Warn, Info, Debug, Trace, etc.)
106+
- **`AdditionalEnvironmentVariables`** - Custom environment variables for advanced scenarios
107+
108+
For detailed configuration guide and best practices, see [Configuration Documentation](docs/CONFIGURATION.md).
109+
79110
#### Manual Configuration
80111

81112
For fine-grained control, you can manually configure each resource:
@@ -126,7 +157,8 @@ The `LocalStack.Aspire.Hosting` host automatically transfers LocalStack configur
126157
- **Manual Configuration**: Fine-grained control with explicit `WithReference()` calls for each resource
127158
- **AWS Service Integration**: Works with CloudFormation templates, CDK stacks, and AWS service clients
128159
- **Automatic Fallback**: Falls back to real AWS services when LocalStack is disabled
129-
- **Container Lifecycle Management**: Configurable container with session/project lifetime options
160+
- **Container Lifecycle Management**: Configurable container with session/persistent lifetime options
161+
- **Eager Service Loading**: Pre-load specific AWS services for faster startup in CI/CD environments
130162
- **Extension-Based**: Works alongside official AWS integrations for .NET Aspire without code changes
131163

132164
## Examples

docs/CONFIGURATION.md

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
# LocalStack Configuration Guide
2+
3+
This guide covers configuration options for customizing LocalStack container behavior in .NET Aspire applications.
4+
5+
## Quick Reference
6+
7+
| Option | Type | Default | Description |
8+
|--------|------|---------|-------------|
9+
| `EagerLoadedServices` | `IReadOnlyCollection<AwsService>` | `[]` (empty) | AWS services to pre-load at container startup |
10+
| `Lifetime` | `ContainerLifetime` | `Persistent` | Container lifecycle behavior |
11+
| `DebugLevel` | `int` | `0` | LocalStack DEBUG flag (0 or 1) |
12+
| `LogLevel` | `LocalStackLogLevel` | `Error` | LocalStack LS_LOG level |
13+
| `AdditionalEnvironmentVariables` | `IDictionary<string, string>` | `{}` (empty) | Custom environment variables |
14+
15+
## Service Loading Strategy
16+
17+
LocalStack supports two service loading strategies via the [`EAGER_SERVICE_LOADING`](https://docs.localstack.cloud/aws/capabilities/config/configuration/#core) configuration.
18+
19+
### Lazy Loading (Default)
20+
21+
Services start only when first accessed. This provides faster initial container startup but adds latency to the first request that uses each service.
22+
23+
```csharp
24+
var localstack = builder.AddLocalStack(); // No eager loading - uses lazy loading
25+
```
26+
27+
**When to use:**
28+
29+
- Local development (faster container startup)
30+
- Exploratory development where service usage isn't predictable
31+
- When working with many services but only using a few
32+
33+
### Eager Loading
34+
35+
Pre-loads specific AWS services during container startup. The container takes longer to start but subsequent requests have no cold-start latency.
36+
37+
```csharp
38+
builder.AddLocalStack(configureContainer: container =>
39+
{
40+
container.EagerLoadedServices = [AwsService.Sqs, AwsService.DynamoDB, AwsService.S3];
41+
});
42+
```
43+
44+
This sets the [`EAGER_SERVICE_LOADING=1`](https://docs.localstack.cloud/aws/capabilities/config/configuration/#core) and [`SERVICES`](https://docs.localstack.cloud/aws/capabilities/config/configuration/#core) environment variables automatically.
45+
46+
**When to use:**
47+
48+
- CI/CD pipelines (consistent startup, no cold-start flakiness)
49+
- Integration tests (eliminate service initialization variance)
50+
- When you know exactly which services you'll use
51+
52+
**Available Services:**
53+
54+
You can eagerly load any AWS service supported by LocalStack. Common examples:
55+
56+
```csharp
57+
container.EagerLoadedServices =
58+
[
59+
AwsService.Sqs, // Simple Queue Service
60+
AwsService.DynamoDB, // DynamoDB
61+
AwsService.S3, // S3 Storage
62+
AwsService.Sns, // Simple Notification Service
63+
AwsService.Lambda, // Lambda Functions
64+
AwsService.CloudFormation,// CloudFormation
65+
AwsService.SecretsManager,// Secrets Manager
66+
AwsService.Ssm, // Systems Manager (Parameter Store)
67+
// ... see LocalStack docs for full list
68+
];
69+
```
70+
71+
For a complete list of supported services, check the [LocalStack health endpoint](https://docs.localstack.cloud/aws/capabilities/networking/internal-endpoints/#localstack-endpoints) at `/_localstack/health`.
72+
73+
## Container Lifetime
74+
75+
Controls when the LocalStack container is created and destroyed.
76+
77+
### Persistent (Default)
78+
79+
Container survives application restarts. Data may persist between debugging sessions depending on your configuration.
80+
81+
```csharp
82+
container.Lifetime = ContainerLifetime.Persistent;
83+
```
84+
85+
**When to use:**
86+
87+
- Local development (container reuse between runs)
88+
- When combined with [LocalStack persistence](https://docs.localstack.cloud/aws/capabilities/state-management/persistence/)
89+
90+
### Session
91+
92+
Container is created when the application starts and destroyed when it stops.
93+
94+
```csharp
95+
container.Lifetime = ContainerLifetime.Session;
96+
```
97+
98+
**When to use:**
99+
100+
- CI/CD pipelines (clean slate for each run)
101+
- Integration tests (isolated test runs)
102+
- When you want guaranteed clean state
103+
104+
**Recommendation:** Use `Session` for CI/CD and integration tests, `Persistent` for local development.
105+
106+
## Logging and Debugging
107+
108+
### Debug Level
109+
110+
Controls LocalStack's [`DEBUG`](https://docs.localstack.cloud/aws/capabilities/config/configuration/#core) flag for increased log verbosity.
111+
112+
```csharp
113+
container.DebugLevel = 1; // Enable verbose logging
114+
```
115+
116+
**Values:**
117+
118+
- `0` (default): Standard log level
119+
- `1`: Increased log level with more verbose logs (useful for troubleshooting)
120+
121+
### Log Level
122+
123+
Controls LocalStack's [`LS_LOG`](https://docs.localstack.cloud/aws/capabilities/config/configuration/#core) environment variable.
124+
125+
```csharp
126+
container.LogLevel = LocalStackLogLevel.Debug;
127+
```
128+
129+
**Available Levels:**
130+
131+
- `Trace`: Detailed request/response logging
132+
- `TraceInternal`: Internal calls logging
133+
- `Debug`: Debug level logging
134+
- `Info`: Info level logging
135+
- `Warn`: Warning level logging
136+
- `Error` (default): Error level logging
137+
- `Warning`: Alias for `Warn`
138+
139+
**Note:** `LS_LOG` currently overrides the `DEBUG` configuration in LocalStack.
140+
141+
For more details on LocalStack logging, see the [official logging documentation](https://docs.localstack.cloud/aws/capabilities/config/logging/).
142+
143+
## Advanced Environment Variables
144+
145+
For advanced scenarios, you can pass custom environment variables to the LocalStack container:
146+
147+
```csharp
148+
container.AdditionalEnvironmentVariables["LOCALSTACK_API_KEY"] = "your-pro-key";
149+
container.AdditionalEnvironmentVariables["PERSISTENCE"] = "1";
150+
```
151+
152+
**Important:** Do not manually set `SERVICES` or `EAGER_SERVICE_LOADING` - these are managed automatically when using `EagerLoadedServices`. An exception will be thrown if conflicts are detected.
153+
154+
**Common Use Cases:**
155+
156+
- LocalStack Pro features ([`LOCALSTACK_API_KEY`](https://docs.localstack.cloud/aws/capabilities/config/configuration/#localstack-pro))
157+
- [Persistence configuration](https://docs.localstack.cloud/aws/capabilities/state-management/persistence/) (`PERSISTENCE`)
158+
- Custom [configuration options](https://docs.localstack.cloud/aws/capabilities/config/configuration/)
159+
160+
## Configuration Patterns
161+
162+
### Development
163+
164+
```csharp
165+
builder.AddLocalStack(configureContainer: container =>
166+
{
167+
container.Lifetime = ContainerLifetime.Persistent;
168+
container.LogLevel = LocalStackLogLevel.Warn;
169+
// Use lazy loading for faster startup
170+
});
171+
```
172+
173+
### CI/CD
174+
175+
```csharp
176+
builder.AddLocalStack(configureContainer: container =>
177+
{
178+
container.Lifetime = ContainerLifetime.Session;
179+
container.LogLevel = LocalStackLogLevel.Error;
180+
181+
// Eagerly load all services used in tests
182+
container.EagerLoadedServices = [AwsService.Sqs, AwsService.DynamoDB, AwsService.S3];
183+
});
184+
```
185+
186+
### Debugging
187+
188+
```csharp
189+
builder.AddLocalStack(configureContainer: container =>
190+
{
191+
container.Lifetime = ContainerLifetime.Session;
192+
container.LogLevel = LocalStackLogLevel.Debug;
193+
container.DebugLevel = 1;
194+
195+
// Eagerly load to see startup issues
196+
container.EagerLoadedServices = [AwsService.Sqs];
197+
});
198+
```
199+
200+
### Integration Testing
201+
202+
```csharp
203+
builder.AddLocalStack(configureContainer: container =>
204+
{
205+
container.Lifetime = ContainerLifetime.Session;
206+
container.LogLevel = LocalStackLogLevel.Error;
207+
208+
// Eagerly load services to avoid cold-start variance
209+
container.EagerLoadedServices = [AwsService.Sqs, AwsService.DynamoDB];
210+
});
211+
```
212+
213+
## Troubleshooting
214+
215+
### Environment Variable Conflicts
216+
217+
If you get an error about environment variable conflicts:
218+
219+
```text
220+
InvalidOperationException: Cannot set 'SERVICES' or 'EAGER_SERVICE_LOADING'
221+
in AdditionalEnvironmentVariables when using EagerLoadedServices.
222+
```
223+
224+
**Solution:** Remove manual `SERVICES` or `EAGER_SERVICE_LOADING` from `AdditionalEnvironmentVariables`:
225+
226+
```csharp
227+
// ❌ DON'T DO THIS
228+
container.AdditionalEnvironmentVariables["SERVICES"] = "sqs,dynamodb";
229+
container.EagerLoadedServices = [AwsService.S3]; // Conflict!
230+
231+
// ✅ DO THIS
232+
container.EagerLoadedServices = [AwsService.Sqs, AwsService.DynamoDB, AwsService.S3];
233+
```
234+
235+
### Container Health Checks Fail
236+
237+
If health checks fail after container starts:
238+
239+
1. **Enable verbose logging:**
240+
241+
```csharp
242+
container.DebugLevel = 1;
243+
container.LogLevel = LocalStackLogLevel.Debug;
244+
```
245+
246+
2. **Check Aspire Dashboard:** Look at health check details for specific failing services
247+
248+
3. **Try eager loading:** Some services may benefit from eager loading:
249+
250+
```csharp
251+
container.EagerLoadedServices = [AwsService.Sqs];
252+
```
253+
254+
### Data Not Persisting
255+
256+
If you expect data to persist between runs:
257+
258+
1. **Check container lifetime:** Ensure `ContainerLifetime.Persistent` is set
259+
2. **Enable persistence:** See [LocalStack Persistence documentation](https://docs.localstack.cloud/aws/capabilities/state-management/persistence/)
260+
261+
```csharp
262+
container.AdditionalEnvironmentVariables["PERSISTENCE"] = "1";
263+
```
264+
265+
## Best Practices
266+
267+
1. **Use Session lifetime in CI/CD** - Ensures clean state for each test run
268+
2. **Eagerly load services in CI** - Eliminates cold-start variability in automated tests
269+
3. **Keep logging minimal in CI** - Use `LogLevel.Error` for cleaner output
270+
4. **Use Persistent lifetime for development** - Faster iteration with container reuse
271+
5. **Start with lazy loading** - Only use eager loading when you have a specific need
272+
6. **Limit eager loaded services** - Only pre-load services you actually use
273+
7. **Document your configuration** - Add comments explaining configuration choices
274+
275+
## Related Documentation
276+
277+
- [README](https://github.com/localstack-dotnet/dotnet-aspire-for-localstack/blob/master/README.md) - Getting started guide
278+
- [LocalStack Configuration](https://docs.localstack.cloud/aws/capabilities/config/configuration/) - Official LocalStack configuration reference
279+
- [LocalStack Persistence](https://docs.localstack.cloud/aws/capabilities/state-management/persistence/) - Data persistence options
280+
- [LocalStack.NET Client](https://github.com/localstack-dotnet/localstack-dotnet-client) - Client-side configuration

src/Aspire.Hosting.LocalStack/Container/LocalStackContainerImageTags.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ internal static class LocalStackContainerImageTags
1818
/// <summary>
1919
/// The default LocalStack container image tag.
2020
/// </summary>
21-
internal const string Tag = "4.9.1";
21+
internal const string Tag = "4.9.2";
2222
}

src/Aspire.Hosting.LocalStack/Internal/Constants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ internal static class Constants
66
internal const int DefaultContainerPort = 4566;
77
internal const string CloudFormationReferenceAnnotation = "Aspire.Hosting.AWS.CloudFormation.CloudFormationReferenceAnnotation";
88
internal const string SQSEventSourceResource = "Aspire.Hosting.AWS.Lambda.SQSEventSourceResource";
9+
10+
internal const string LocalStackHealthClientName = "localstack_health_client";
11+
internal const string LocalStackHealthCheckName = "localstack_health";
912
}

0 commit comments

Comments
 (0)