LayeredCraft.Cdk.Constructs
0.1.0-beta.6
See the version list below for details.
dotnet add package LayeredCraft.Cdk.Constructs --version 0.1.0-beta.6
NuGet\Install-Package LayeredCraft.Cdk.Constructs -Version 0.1.0-beta.6
<PackageReference Include="LayeredCraft.Cdk.Constructs" Version="0.1.0-beta.6" />
<PackageVersion Include="LayeredCraft.Cdk.Constructs" Version="0.1.0-beta.6" />
<PackageReference Include="LayeredCraft.Cdk.Constructs" />
paket add LayeredCraft.Cdk.Constructs --version 0.1.0-beta.6
#r "nuget: LayeredCraft.Cdk.Constructs, 0.1.0-beta.6"
#:package LayeredCraft.Cdk.Constructs@0.1.0-beta.6
#addin nuget:?package=LayeredCraft.Cdk.Constructs&version=0.1.0-beta.6&prerelease
#tool nuget:?package=LayeredCraft.Cdk.Constructs&version=0.1.0-beta.6&prerelease
cdk-constructs
A reusable library of AWS CDK constructs for .NET projects, optimized for serverless applications using Lambda, API Gateway (or Lambda URLs), DynamoDB, S3, CloudFront, and OpenTelemetry. Built for speed, observability, and cost efficiency across the LayeredCraft project ecosystem.
Installation
Install the package via NuGet:
dotnet add package LayeredCraft.Cdk.Constructs
Or via Package Manager Console:
Install-Package LayeredCraft.Cdk.Constructs
Quick Start
Basic Lambda Function
using Amazon.CDK;
using Amazon.CDK.AWS.IAM;
using LayeredCraft.Cdk.Constructs.Constructs;
using LayeredCraft.Cdk.Constructs.Models;
public class MyStack : Stack
{
public MyStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props)
{
var lambdaConstruct = new LambdaFunctionConstruct(this, "MyLambda", new LambdaFunctionConstructProps
{
FunctionName = "my-function",
FunctionSuffix = "prod",
AssetPath = "./lambda-deployment.zip",
RoleName = "my-function-role",
PolicyName = "my-function-policy",
EnvironmentVariables = new Dictionary<string, string>
{
{ "ENVIRONMENT", "production" },
{ "LOG_LEVEL", "info" }
}
});
}
}
Lambda with Custom IAM Permissions
var lambdaConstruct = new LambdaFunctionConstruct(this, "MyLambda", new LambdaFunctionConstructProps
{
FunctionName = "my-function",
FunctionSuffix = "prod",
AssetPath = "./lambda-deployment.zip",
RoleName = "my-function-role",
PolicyName = "my-function-policy",
PolicyStatements = new[]
{
new PolicyStatement(new PolicyStatementProps
{
Actions = new[] { "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:Query" },
Resources = new[] { "arn:aws:dynamodb:us-east-1:123456789012:table/MyTable" },
Effect = Effect.ALLOW
})
},
Permissions = new List<LambdaPermission>
{
new LambdaPermission
{
Principal = "apigateway.amazonaws.com",
Action = "lambda:InvokeFunction",
SourceArn = "arn:aws:execute-api:us-east-1:123456789012:*/*/GET/my-endpoint"
}
}
});
Features
🚀 LambdaFunctionConstruct
The main construct that creates a complete Lambda function setup with:
- Lambda Function: Configured for custom runtimes (
PROVIDED_AL2023
) with bootstrap handler - IAM Role & Policy: Automatic CloudWatch Logs permissions + custom policy statements
- CloudWatch Logs: Explicit log group with configurable retention (default: 2 weeks)
- OpenTelemetry: Optional AWS OTEL Collector layer integration
- Versioning: Automatic version creation with "live" alias
- Multi-target Permissions: Applies permissions to function, version, and alias
🔧 Configuration Options
Property | Type | Description | Default |
---|---|---|---|
FunctionName |
string |
Base name for the Lambda function | Required |
FunctionSuffix |
string |
Suffix appended to function name | Required |
AssetPath |
string |
Path to Lambda deployment package | Required |
RoleName |
string |
IAM role name | Required |
PolicyName |
string |
IAM policy name | Required |
PolicyStatements |
PolicyStatement[] |
Custom IAM policy statements | [] |
EnvironmentVariables |
IDictionary<string, string> |
Environment variables | {} |
IncludeOtelLayer |
bool |
Enable OpenTelemetry layer | true |
Permissions |
List<LambdaPermission> |
Lambda invocation permissions | [] |
🔍 OpenTelemetry Integration
When IncludeOtelLayer
is enabled (default), the construct automatically:
- Adds the AWS OTEL Collector layer
- Enables X-Ray tracing
- Configures the Lambda for observability
📋 Automatic IAM Permissions
The construct automatically grants these CloudWatch Logs permissions:
logs:CreateLogStream
logs:CreateLogGroup
logs:TagResource
logs:PutLogEvents
Testing Support
The library includes comprehensive testing helpers to make it easy to unit test your CDK stacks that use these constructs.
Testing Helpers Overview
CdkTestHelper
: Reduces boilerplate for creating test stacks and props (including custom stack types)LambdaFunctionConstructAssertions
: Extension methods for asserting Lambda resourcesLambdaFunctionConstructPropsBuilder
: Fluent builder for creating test props
Quick Testing Example
using LayeredCraft.Cdk.Constructs.Constructs;
using LayeredCraft.Cdk.Constructs.Testing;
[Fact]
public void MyStack_ShouldCreateApiFunction()
{
// Create test infrastructure with minimal boilerplate
var stack = CdkTestHelper.CreateTestStackMinimal();
// Build test props using fluent builder
var props = CdkTestHelper.CreatePropsBuilder("./my-lambda.zip")
.WithFunctionName("my-api")
.WithFunctionSuffix("prod")
.WithDynamoDbAccess("users-table")
.WithApiGatewayPermission("arn:aws:execute-api:us-east-1:123456789012:abcdef123/prod/GET/users")
.WithEnvironmentVariable("TABLE_NAME", "users-table")
.WithOtelEnabled(true)
.Build();
// Create the construct
_ = new LambdaFunctionConstruct(stack, "MyApiLambda", props);
// Create template AFTER adding constructs to the stack
var template = Template.FromStack(stack);
// Use assertion helpers for clean, readable tests
template.ShouldHaveLambdaFunction("my-api-prod");
template.ShouldHaveOtelLayer();
template.ShouldHaveCloudWatchLogsPermissions("my-api-prod");
template.ShouldHaveEnvironmentVariables(new Dictionary<string, string>
{
{ "TABLE_NAME", "users-table" }
});
template.ShouldHaveLambdaPermissions(1); // 1 permission = 3 resources (function + version + alias)
template.ShouldHaveVersionAndAlias("live");
template.ShouldHaveLogGroup("my-api-prod", 14);
}
Example: Testing Without OpenTelemetry
[Fact]
public void MyStack_ShouldCreateLegacyFunction()
{
var stack = CdkTestHelper.CreateTestStackMinimal();
var props = CdkTestHelper.CreatePropsBuilder("./legacy-lambda.zip")
.WithFunctionName("legacy-function")
.WithOtelEnabled(false)
.Build();
_ = new LambdaFunctionConstruct(stack, "LegacyLambda", props);
// Create template AFTER adding constructs to the stack
var template = Template.FromStack(stack);
template.ShouldHaveLambdaFunction("legacy-function-test");
template.ShouldNotHaveOtelLayer();
template.ShouldHaveCloudWatchLogsPermissions("legacy-function-test");
}
Example: Testing Custom Stack Types
For projects with custom stack implementations (inheriting from Stack
), you can use the generic methods to create your custom stack types directly:
[Fact]
public void MyCustomStack_ShouldCreateInfrastructure()
{
// Custom stack props (could be your LightsaberStackProps, InfraStackProps, etc.)
var customProps = new MyCustomStackProps
{
Env = new Environment { Account = "123456789012", Region = "us-east-1" },
Context = new MyContext { /* your custom context */ },
Tags = new Dictionary<string, string> { { "Environment", "test" } }
};
// Create your custom stack type directly - no unused base stack needed!
var customStack = CdkTestHelper.CreateTestStackMinimal<MyCustomStack>("test-stack", customProps);
// Your custom stack is ready to use
var template = Template.FromStack(customStack);
// Verify your custom stack's infrastructure
template.ShouldHaveLambdaFunction("my-function-test");
// ... other assertions
}
Benefits of Generic Stack Creation:
- No Workarounds: Create custom stack types directly instead of creating unused base stacks
- Type Safety: Get strongly-typed stack instances with full IntelliSense support
- Clean Tests: Eliminate boilerplate while maintaining flexibility
- Real-World Ready: Works with any custom stack implementation
- Constructor Support: Supports both public and internal constructors (CDK default)
Example: Testing with Custom Stack Props Types
For advanced scenarios where your custom stack expects specific props interfaces (not just IStackProps
), you can use the dual-generic methods:
[Fact]
public void MyAdvancedStack_ShouldWorkWithCustomProps()
{
// Custom props interface extending IStackProps
var customProps = new MyCustomStackProps
{
Env = new Environment { Account = "123456789012", Region = "us-east-1" },
Context = new MyContext { DatabaseUrl = "test-db-url" },
FeatureFlags = new Dictionary<string, bool> { { "EnableNewFeature", true } }
};
// Use dual generics for exact type matching - solves Activator.CreateInstance issues
var stack = CdkTestHelper.CreateTestStackMinimal<MyAdvancedStack, MyCustomStackProps>(
"advanced-test", customProps);
// Your stack gets the exact props type it expects
var template = Template.FromStack(stack);
// Test with full type safety
template.ShouldHaveLambdaFunction("advanced-function");
stack.DatabaseUrl.Should().Be("test-db-url"); // Custom props available
stack.IsNewFeatureEnabled.Should().Be(true);
}
When to Use Dual Generics:
- Your stack constructor expects a specific props interface (e.g.,
ILightsaberStackProps
,IInfraStackProps
) - You need exact type matching for
Activator.CreateInstance
to work correctly - You want full IntelliSense support for your custom props throughout the test
Supported Constructor Patterns
The generic methods work with both public and internal constructors using reflection with BindingFlags
:
// ✅ Works - Public constructor
public class MyStack : Stack
{
public MyStack(Construct scope, string id, IStackProps props) : base(scope, id, props) { }
}
// ✅ Works - Internal constructor (CDK default pattern)
public class MyStack : Stack
{
internal MyStack(Construct scope, string id, IStackProps props) : base(scope, id, props) { }
}
// ✅ Works - Custom props interface with internal constructor
public class MyStack : Stack
{
internal MyStack(Construct scope, string id, IMyCustomProps props) : base(scope, id, props) { }
}
Important Notes:
- The helpers use
BindingFlags.NonPublic
to access internal constructors - This follows common testing library patterns for accessing non-public members
- Works seamlessly with CDK's default internal constructor pattern
Test Asset Management
Create a TestAssets
folder in your test project and place your Lambda deployment packages there:
YourTestProject/
├── TestAssets/
│ ├── test-lambda.zip # Default test asset
│ └── my-custom-lambda.zip # Custom test assets
└── YourTests.cs
The testing helpers automatically resolve test asset paths:
// Uses TestAssets/test-lambda.zip by default
var props = CdkTestHelper.CreatePropsBuilder().Build();
// Or specify your own test asset
var props = CdkTestHelper.CreatePropsBuilder("./TestAssets/my-custom-lambda.zip").Build();
// Get reliable paths to test assets
var assetPath = CdkTestHelper.GetTestAssetPath("TestAssets/my-lambda.zip");
Available Assertion Methods
Method | Description |
---|---|
ShouldHaveLambdaFunction(functionName) |
Verify Lambda function exists |
ShouldHaveOtelLayer() |
Verify OpenTelemetry layer and tracing are enabled |
ShouldNotHaveOtelLayer() |
Verify OpenTelemetry is disabled |
ShouldHaveCloudWatchLogsPermissions(functionName) |
Verify log permissions |
ShouldHaveEnvironmentVariables(variables) |
Verify environment variables |
ShouldHaveLambdaPermissions(count) |
Verify Lambda invoke permissions |
ShouldHaveVersionAndAlias(aliasName) |
Verify versioning setup |
ShouldHaveLogGroup(functionName, retentionDays) |
Verify log group configuration |
Props Builder Methods
Method | Description |
---|---|
WithFunctionName(name) |
Set function name |
WithFunctionSuffix(suffix) |
Set function suffix |
WithAssetPath(path) |
Set Lambda deployment package path |
WithOtelEnabled(bool) |
Enable/disable OpenTelemetry |
WithDynamoDbAccess(tableName) |
Add DynamoDB permissions |
WithS3Access(bucketName) |
Add S3 permissions |
WithApiGatewayPermission(apiArn) |
Add API Gateway invoke permission |
WithAlexaPermission(skillId) |
Add Alexa Skills Kit permission |
WithEnvironmentVariable(key, value) |
Add environment variable |
WithEnvironmentVariables(variables) |
Add multiple environment variables |
WithCustomPolicy(policyStatement) |
Add custom IAM policy statement |
WithCustomPermission(permission) |
Add custom Lambda permission |
Project Structure
├── src/
│ └── LayeredCraft.Cdk.Constructs/ # Main library package
│ ├── Constructs/
│ │ └── LambdaFunctionConstruct.cs # Lambda function construct with IAM and logging
│ ├── Models/
│ │ ├── LambdaFunctionConstructProps.cs # Configuration props for Lambda construct
│ │ └── LambdaPermission.cs # Lambda permission model
│ └── Testing/ # Testing helpers for consumers
│ ├── CdkTestHelper.cs # Test stack and props creation utilities
│ ├── LambdaFunctionConstructAssertions.cs # Extension methods for assertions
│ └── LambdaFunctionConstructPropsBuilder.cs # Fluent builder for test props
├── test/
│ └── LayeredCraft.Cdk.Constructs.Tests/ # Test suite
│ ├── Constructs/
│ │ └── LambdaFunctionConstructTests.cs # Unit tests for Lambda construct
│ ├── Testing/
│ │ └── TestingHelpersTests.cs # Tests for testing helper functionality
│ ├── TestKit/ # Test utilities and fixtures
│ │ ├── Attributes/ # Custom AutoFixture attributes
│ │ ├── Customizations/ # Test data customizations
│ │ └── Extensions/ # Test extension methods
│ └── TestAssets/
│ └── test-lambda.zip # Test Lambda deployment package
└── LayeredCraft.Cdk.Constructs.sln # Solution file
Requirements
- .NET 8.0 or .NET 9.0
- AWS CDK v2 (Amazon.CDK.Lib 2.203.1+)
- AWS CLI configured with appropriate permissions
- Node.js (for CDK deployment)
Runtime Configuration
The Lambda functions created by this construct use:
- Runtime:
PROVIDED_AL2023
(Amazon Linux 2023) - Handler:
bootstrap
(for custom runtimes) - Memory: 1024 MB
- Timeout: 6 seconds
- Log Retention: 2 weeks
Development
Building the Project
# Build the solution
dotnet build
# Run tests
dotnet run --project test/LayeredCraft.Cdk.Constructs.Tests/ --framework net8.0
# Pack for local testing
dotnet pack src/LayeredCraft.Cdk.Constructs/
Testing
The project uses xUnit v3 with AutoFixture for comprehensive testing:
# Run all tests
cd test/LayeredCraft.Cdk.Constructs.Tests
dotnet run --framework net8.0
# Run specific test
dotnet run --framework net8.0 -- --filter "Construct_ShouldCreateLambdaFunction"
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
For issues and questions:
Built with ❤️ by the LayeredCraft team
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Amazon.CDK.Lib (>= 2.203.1)
-
net9.0
- Amazon.CDK.Lib (>= 2.203.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.0.1.22 | 41 | 7/18/2025 |
1.0.0.21 | 163 | 7/11/2025 |
1.0.0.20 | 143 | 7/10/2025 |
1.0.0.19 | 128 | 7/10/2025 |
1.0.0.18 | 127 | 7/10/2025 |
1.0.0.17 | 134 | 7/9/2025 |
1.0.0.16 | 126 | 7/9/2025 |
1.0.0.15 | 134 | 7/9/2025 |
1.0.0.14 | 132 | 7/9/2025 |
1.0.0.13 | 132 | 7/9/2025 |
1.0.0.12 | 133 | 7/9/2025 |
1.0.0.11 | 133 | 7/9/2025 |
0.1.0-beta.10 | 116 | 7/9/2025 |
0.1.0-beta.9 | 113 | 7/8/2025 |
0.1.0-beta.8 | 119 | 7/8/2025 |
0.1.0-beta.7 | 109 | 7/7/2025 |
0.1.0-beta.6 | 110 | 7/4/2025 |
0.1.0-beta.5 | 103 | 7/4/2025 |
0.1.0-beta.4 | 108 | 7/3/2025 |
0.1.0-beta.3 | 106 | 7/3/2025 |
0.1.0-beta.2 | 116 | 7/3/2025 |
0.1.0-beta.1 | 119 | 7/3/2025 |