Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 23 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ dotnet add test/LayeredCraft.Cdk.Constructs.Tests/ package PackageName
**LambdaFunctionConstruct** (`src/LayeredCraft.Cdk.Constructs/LambdaFunctionConstruct.cs`)
- Main CDK construct that creates Lambda functions with comprehensive configuration
- Automatically creates IAM roles and policies with CloudWatch Logs permissions
- Supports OpenTelemetry layer integration via AWS managed layer
- Supports configurable OpenTelemetry layer integration via AWS managed layer (disabled by default)
- Configurable OTEL layer version and architecture (amd64/arm64)
- Supports AWS Lambda SnapStart for improved cold start performance
- Creates function versions and aliases for deployment management
- Handles Lambda permissions for multiple targets (function, version, alias)
Expand Down Expand Up @@ -82,13 +83,32 @@ dotnet add test/LayeredCraft.Cdk.Constructs.Tests/ package PackageName
1. **Construct Pattern**: Uses AWS CDK construct pattern for reusable infrastructure components
2. **Interface + Record Pattern**: Props classes use both interface and record for flexibility and immutability
3. **Multi-Target Permissions**: Automatically applies permissions to function, version, and alias
4. **OpenTelemetry Integration**: Built-in support for AWS OTEL collector layer
4. **OpenTelemetry Integration**: Configurable support for AWS OTEL collector layer with version and architecture options
5. **Versioning Strategy**: Creates new versions on every deployment with "live" alias

### Target Frameworks
- .NET 8.0 and .NET 9.0
- Uses AWS CDK v2 (Amazon.CDK.Lib 2.203.1)

### OpenTelemetry Configuration (v2.0.0+)
Starting with version 2.0.0, the OpenTelemetry layer configuration has been updated:

- **Default Behavior**: OTEL layer is now **disabled by default** (breaking change from v1.x)
- **Architecture Support**: Configurable architecture via `Architecture` property (default: "amd64")
- **Version Control**: Configurable OTEL layer version via `OtelLayerVersion` property (default: "0-117-0")
- **Layer ARN Format**: `arn:aws:lambda:{region}:901920570463:layer:aws-otel-collector-{architecture}-ver-{version}:1`

To enable OTEL layer in v2.0.0+:
```csharp
var props = new LambdaFunctionConstructProps
{
// ... other properties
IncludeOtelLayer = true, // Explicitly enable
Architecture = "arm64", // Optional: change architecture
OtelLayerVersion = "0-117-0" // Optional: specify version
};
```

## Testing Framework

### Test Structure
Expand Down Expand Up @@ -179,7 +199,7 @@ The library includes comprehensive testing helpers for consumers:

### AWS CDK Patterns
- Uses `PROVIDED_AL2023` runtime for Lambda functions (supports custom runtimes)
- Hardcoded OpenTelemetry layer ARN for us-east-1 region
- Configurable OpenTelemetry layer version and architecture (defaults to version 0-117-0, amd64 architecture)
- Default memory: 1024MB, timeout: 6 seconds, log retention: 2 weeks
- Uses `RemovalPolicy.RETAIN` for Lambda versions to prevent deletion

Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.0.1</VersionPrefix>
<VersionPrefix>2.0.0-beta</VersionPrefix>
<!-- SPDX license identifier for MIT -->
<PackageLicenseExpression>MIT</PackageLicenseExpression>

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A comprehensive library of reusable AWS CDK constructs for .NET projects, design

## Features

- **🚀 Lambda Functions**: Comprehensive Lambda construct with OpenTelemetry support, IAM management, and environment configuration
- **🚀 Lambda Functions**: Comprehensive Lambda construct with configurable OpenTelemetry support, IAM management, and environment configuration
- **🌐 Static Sites**: Complete static website hosting with S3, CloudFront, SSL certificates, and Route53 DNS management
- **📊 DynamoDB Tables**: Full-featured DynamoDB construct with streams, TTL, and global secondary indexes
- **🧪 Testing Helpers**: Extensive testing utilities with fluent assertions and builders
Expand Down Expand Up @@ -45,6 +45,8 @@ public class MyStack : Stack
RoleName = "my-api-role",
PolicyName = "my-api-policy",
GenerateUrl = true, // Creates Function URL for HTTP access
IncludeOtelLayer = true, // Enable OpenTelemetry (disabled by default in v2.0+)
Architecture = "arm64", // Optional: specify architecture (default: amd64)
EnvironmentVariables = new Dictionary<string, string>
{
{ "ENVIRONMENT", "production" },
Expand Down
34 changes: 28 additions & 6 deletions docs/constructs/lambda-function.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Lambda Function Construct

The `LambdaFunctionConstruct` provides a comprehensive, production-ready Lambda function with integrated OpenTelemetry support, IAM management, and environment configuration.
The `LambdaFunctionConstruct` provides a comprehensive, production-ready Lambda function with configurable OpenTelemetry support, IAM management, and environment configuration.

## :rocket: Features

- **:chart_with_upwards_trend: OpenTelemetry Integration**: Built-in AWS OpenTelemetry collector layer
- **:chart_with_upwards_trend: OpenTelemetry Integration**: Configurable AWS OpenTelemetry collector layer with version and architecture support
- **:shield: IAM Management**: Automatic role and policy creation with CloudWatch Logs permissions
- **:gear: Environment Configuration**: Easy environment variable management
- **:link: Function URLs**: Optional HTTP endpoint generation
Expand All @@ -29,7 +29,8 @@ public class MyStack : Stack
FunctionSuffix = "prod",
AssetPath = "./lambda-deployment.zip",
RoleName = "my-api-role",
PolicyName = "my-api-policy"
PolicyName = "my-api-policy",
IncludeOtelLayer = true // Enable OpenTelemetry (disabled by default in v2.0+)
});
}
}
Expand All @@ -55,7 +56,9 @@ public class MyStack : Stack
| `TimeoutInSeconds` | `double` | `6` | Function timeout in seconds |
| `PolicyStatements` | `PolicyStatement[]` | `[]` | Additional IAM policy statements |
| `EnvironmentVariables` | `IDictionary<string, string>` | `{}` | Environment variables |
| `IncludeOtelLayer` | `bool` | `true` | Enable OpenTelemetry layer |
| `IncludeOtelLayer` | `bool` | `false` | Enable OpenTelemetry layer |
| `OtelLayerVersion` | `string` | `"0-117-0"` | OpenTelemetry layer version |
| `Architecture` | `string` | `"amd64"` | Lambda architecture (amd64/arm64) |
| `Permissions` | `List<LambdaPermission>` | `[]` | Lambda invocation permissions |
| `EnableSnapStart` | `bool` | `false` | Enable SnapStart for improved cold starts |
| `GenerateUrl` | `bool` | `false` | Generate Function URL for HTTP access |
Expand Down Expand Up @@ -142,6 +145,25 @@ var lambda = new LambdaFunctionConstruct(this, "MyLambda", new LambdaFunctionCon
});
```

### Lambda with OpenTelemetry Configuration

```csharp
var lambda = new LambdaFunctionConstruct(this, "MyLambda", new LambdaFunctionConstructProps
{
FunctionName = "my-api",
FunctionSuffix = "prod",
AssetPath = "./lambda-deployment.zip",
RoleName = "my-api-role",
PolicyName = "my-api-policy",
IncludeOtelLayer = true, // Enable OpenTelemetry layer
Architecture = "arm64", // Use ARM64 architecture
OtelLayerVersion = "0-117-0" // Specify OTEL layer version
});
```

!!! warning "Breaking Change in v2.0.0"
Starting with version 2.0.0, the OpenTelemetry layer is **disabled by default**. You must explicitly set `IncludeOtelLayer = true` to enable it. This change allows for better control over observability costs and layer dependencies.

## Public Properties

### LambdaFunction
Expand Down Expand Up @@ -175,9 +197,9 @@ The Lambda functions use the following runtime configuration:
!!! info "Runtime Details"
- **Runtime**: `PROVIDED_AL2023` (Amazon Linux 2023)
- **Handler**: `bootstrap` (for custom runtimes)
- **Architecture**: x86_64
- **Architecture**: Configurable (amd64/arm64, default: amd64)
- **Log Retention**: 2 weeks
- **OpenTelemetry Layer**: AWS managed layer (us-east-1 region)
- **OpenTelemetry Layer**: Configurable AWS managed layer (disabled by default in v2.0+)

## IAM Permissions

Expand Down
2 changes: 1 addition & 1 deletion src/LayeredCraft.Cdk.Constructs/LambdaFunctionConstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public LambdaFunctionConstruct(Construct scope, string id, ILambdaFunctionConstr
if (props.IncludeOtelLayer)
{
LambdaFunction.AddLayers(LayerVersion.FromLayerVersionArn(this, "OTELLambdaLayer",
$"arn:aws:lambda:{region}:901920570463:layer:aws-otel-collector-amd64-ver-0-102-1:1"));
$"arn:aws:lambda:{region}:901920570463:layer:aws-otel-collector-{props.Architecture}-ver-{props.OtelLayerVersion}:1"));
}

// ✅ Create a new version on every deployment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Amazon.CDK.Lib" Version="2.206.0" />
<PackageReference Include="Amazon.CDK.Lib" Version="2.209.1" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\docs\assets\icon.png" Pack="true" PackagePath="" Visible="False" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public interface ILambdaFunctionConstructProps
PolicyStatement[] PolicyStatements { get; set; }
IDictionary<string, string> EnvironmentVariables { get; set; }
bool IncludeOtelLayer { get; set; }
string OtelLayerVersion { get; set; }
string Architecture { get; set; }
List<LambdaPermission> Permissions { get; set; }
bool EnableSnapStart { get; set; }
bool GenerateUrl { get; set; }
Expand All @@ -29,8 +31,10 @@ public sealed record LambdaFunctionConstructProps : ILambdaFunctionConstructProp
public double TimeoutInSeconds { get; set; } = 6;
public PolicyStatement[] PolicyStatements { get; set; } = [];
public IDictionary<string, string> EnvironmentVariables { get; set; } = new Dictionary<string, string>();
public bool IncludeOtelLayer { get; set; } = true;
public bool IncludeOtelLayer { get; set; } = false;
public string OtelLayerVersion { get; set; } = "0-117-0";
public string Architecture { get; set; } = "amd64";
public List<LambdaPermission> Permissions { get; set; } = [];
public bool EnableSnapStart { get; set; } = false;
public bool GenerateUrl { get; set; } = false;
public bool EnableSnapStart { get; set; }
public bool GenerateUrl { get; set; }
Comment on lines +38 to +39
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The default value assignment has been removed from these properties. Consider maintaining explicit default values for consistency with other properties in the class.

Suggested change
public bool EnableSnapStart { get; set; }
public bool GenerateUrl { get; set; }
public bool EnableSnapStart { get; set; } = false;
public bool GenerateUrl { get; set; } = false;

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +39
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The default value assignment has been removed from these properties. Consider maintaining explicit default values for consistency with other properties in the class.

Suggested change
public bool EnableSnapStart { get; set; }
public bool GenerateUrl { get; set; }
public bool EnableSnapStart { get; set; } = false;
public bool GenerateUrl { get; set; } = false;

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ public class LambdaFunctionConstructPropsBuilder
private double _timeoutInSeconds = 6;
private readonly List<PolicyStatement> _policyStatements = new();
private readonly Dictionary<string, string> _environmentVariables = new();
private bool _includeOtelLayer = true;
private bool _includeOtelLayer = false;
private string _otelLayerVersion = "0-117-0";
private string _architecture = "amd64";
private readonly List<LambdaPermission> _permissions = new();
private bool _enableSnapStart = false;
private bool _generateUrl = false;
Expand Down Expand Up @@ -89,6 +91,28 @@ public LambdaFunctionConstructPropsBuilder WithOtelEnabled(bool enabled = true)
return this;
}

/// <summary>
/// Sets the OpenTelemetry layer version.
/// </summary>
/// <param name="version">The OTEL layer version (e.g., "0-117-0")</param>
/// <returns>The builder instance for method chaining</returns>
public LambdaFunctionConstructPropsBuilder WithOtelLayerVersion(string version)
{
_otelLayerVersion = version;
return this;
}

/// <summary>
/// Sets the Lambda function architecture.
/// </summary>
/// <param name="architecture">The architecture (e.g., "amd64", "arm64")</param>
/// <returns>The builder instance for method chaining</returns>
public LambdaFunctionConstructPropsBuilder WithArchitecture(string architecture)
{
_architecture = architecture;
return this;
}

/// <summary>
/// Adds DynamoDB access permissions for the specified table.
/// </summary>
Expand Down Expand Up @@ -273,6 +297,8 @@ public LambdaFunctionConstructProps Build()
PolicyStatements = [.. _policyStatements],
EnvironmentVariables = _environmentVariables,
IncludeOtelLayer = _includeOtelLayer,
OtelLayerVersion = _otelLayerVersion,
Architecture = _architecture,
Permissions = _permissions,
EnableSnapStart = _enableSnapStart,
GenerateUrl = _generateUrl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public void Construct_ShouldIncludeOtelLayerWhenEnabled(LambdaFunctionConstructP
template.HasResourceProperties("AWS::Lambda::Function", Match.ObjectLike(new Dictionary<string, object>
{
{ "TracingConfig", new Dictionary<string, object> { { "Mode", "Active" } } },
{ "Layers", new object[] { Match.StringLikeRegexp(".*aws-otel-collector.*") } }
{ "Layers", new object[] { Match.StringLikeRegexp(".*aws-otel-collector-amd64-ver-0-117-0.*") } }
}));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace LayeredCraft.Cdk.Constructs.Tests.TestKit.Attributes;

public class LambdaFunctionConstructAutoDataAttribute(bool includeOtelLayer = true, bool includePermissions = true, bool generateUrl = false) : AutoDataAttribute(() => CreateFixture(includeOtelLayer, includePermissions, generateUrl))
public class LambdaFunctionConstructAutoDataAttribute(bool includeOtelLayer = false, bool includePermissions = true, bool generateUrl = false) : AutoDataAttribute(() => CreateFixture(includeOtelLayer, includePermissions, generateUrl))
{
private static IFixture CreateFixture(bool includeOtelLayer, bool includePermissions, bool generateUrl)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace LayeredCraft.Cdk.Constructs.Tests.TestKit.Customizations;

public class LambdaFunctionConstructCustomization(bool includeOtelLayer = true, bool includePermissions = true, bool generateUrl = false)
public class LambdaFunctionConstructCustomization(bool includeOtelLayer = false, bool includePermissions = true, bool generateUrl = false)
: ICustomization
{
public void Customize(IFixture fixture)
Expand Down Expand Up @@ -33,6 +33,8 @@ public void Customize(IFixture fixture)
{ "TEST_VAR", "test-value" }
})
.With(props => props.IncludeOtelLayer, includeOtelLayer)
.With(props => props.OtelLayerVersion, "0-117-0")
.With(props => props.Architecture, "amd64")
.With(props => props.Permissions, includePermissions
? [fixture.Create<LambdaPermission>()]
: [])
Expand Down