diff --git a/CLAUDE.md b/CLAUDE.md index 6536562..b0026c6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) @@ -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 @@ -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 diff --git a/Directory.Build.props b/Directory.Build.props index db28717..84adee2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 1.0.1 + 2.0.0-beta MIT diff --git a/README.md b/README.md index bdc61d8..9d0616a 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 { { "ENVIRONMENT", "production" }, diff --git a/docs/constructs/lambda-function.md b/docs/constructs/lambda-function.md index 9400537..1a45031 100644 --- a/docs/constructs/lambda-function.md +++ b/docs/constructs/lambda-function.md @@ -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 @@ -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+) }); } } @@ -55,7 +56,9 @@ public class MyStack : Stack | `TimeoutInSeconds` | `double` | `6` | Function timeout in seconds | | `PolicyStatements` | `PolicyStatement[]` | `[]` | Additional IAM policy statements | | `EnvironmentVariables` | `IDictionary` | `{}` | 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` | `[]` | Lambda invocation permissions | | `EnableSnapStart` | `bool` | `false` | Enable SnapStart for improved cold starts | | `GenerateUrl` | `bool` | `false` | Generate Function URL for HTTP access | @@ -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 @@ -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 diff --git a/src/LayeredCraft.Cdk.Constructs/LambdaFunctionConstruct.cs b/src/LayeredCraft.Cdk.Constructs/LambdaFunctionConstruct.cs index 2f57adb..b30e906 100644 --- a/src/LayeredCraft.Cdk.Constructs/LambdaFunctionConstruct.cs +++ b/src/LayeredCraft.Cdk.Constructs/LambdaFunctionConstruct.cs @@ -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 diff --git a/src/LayeredCraft.Cdk.Constructs/LayeredCraft.Cdk.Constructs.csproj b/src/LayeredCraft.Cdk.Constructs/LayeredCraft.Cdk.Constructs.csproj index 4f2db92..1dbeadd 100644 --- a/src/LayeredCraft.Cdk.Constructs/LayeredCraft.Cdk.Constructs.csproj +++ b/src/LayeredCraft.Cdk.Constructs/LayeredCraft.Cdk.Constructs.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/LayeredCraft.Cdk.Constructs/Models/LambdaFunctionConstructProps.cs b/src/LayeredCraft.Cdk.Constructs/Models/LambdaFunctionConstructProps.cs index 3114e63..d787bfc 100644 --- a/src/LayeredCraft.Cdk.Constructs/Models/LambdaFunctionConstructProps.cs +++ b/src/LayeredCraft.Cdk.Constructs/Models/LambdaFunctionConstructProps.cs @@ -14,6 +14,8 @@ public interface ILambdaFunctionConstructProps PolicyStatement[] PolicyStatements { get; set; } IDictionary EnvironmentVariables { get; set; } bool IncludeOtelLayer { get; set; } + string OtelLayerVersion { get; set; } + string Architecture { get; set; } List Permissions { get; set; } bool EnableSnapStart { get; set; } bool GenerateUrl { get; set; } @@ -29,8 +31,10 @@ public sealed record LambdaFunctionConstructProps : ILambdaFunctionConstructProp public double TimeoutInSeconds { get; set; } = 6; public PolicyStatement[] PolicyStatements { get; set; } = []; public IDictionary EnvironmentVariables { get; set; } = new Dictionary(); - 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 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; } } \ No newline at end of file diff --git a/src/LayeredCraft.Cdk.Constructs/Testing/LambdaFunctionConstructPropsBuilder.cs b/src/LayeredCraft.Cdk.Constructs/Testing/LambdaFunctionConstructPropsBuilder.cs index 1fe2d32..c1b5cc0 100644 --- a/src/LayeredCraft.Cdk.Constructs/Testing/LambdaFunctionConstructPropsBuilder.cs +++ b/src/LayeredCraft.Cdk.Constructs/Testing/LambdaFunctionConstructPropsBuilder.cs @@ -18,7 +18,9 @@ public class LambdaFunctionConstructPropsBuilder private double _timeoutInSeconds = 6; private readonly List _policyStatements = new(); private readonly Dictionary _environmentVariables = new(); - private bool _includeOtelLayer = true; + private bool _includeOtelLayer = false; + private string _otelLayerVersion = "0-117-0"; + private string _architecture = "amd64"; private readonly List _permissions = new(); private bool _enableSnapStart = false; private bool _generateUrl = false; @@ -89,6 +91,28 @@ public LambdaFunctionConstructPropsBuilder WithOtelEnabled(bool enabled = true) return this; } + /// + /// Sets the OpenTelemetry layer version. + /// + /// The OTEL layer version (e.g., "0-117-0") + /// The builder instance for method chaining + public LambdaFunctionConstructPropsBuilder WithOtelLayerVersion(string version) + { + _otelLayerVersion = version; + return this; + } + + /// + /// Sets the Lambda function architecture. + /// + /// The architecture (e.g., "amd64", "arm64") + /// The builder instance for method chaining + public LambdaFunctionConstructPropsBuilder WithArchitecture(string architecture) + { + _architecture = architecture; + return this; + } + /// /// Adds DynamoDB access permissions for the specified table. /// @@ -273,6 +297,8 @@ public LambdaFunctionConstructProps Build() PolicyStatements = [.. _policyStatements], EnvironmentVariables = _environmentVariables, IncludeOtelLayer = _includeOtelLayer, + OtelLayerVersion = _otelLayerVersion, + Architecture = _architecture, Permissions = _permissions, EnableSnapStart = _enableSnapStart, GenerateUrl = _generateUrl diff --git a/test/LayeredCraft.Cdk.Constructs.Tests/LambdaFunctionConstructTests.cs b/test/LayeredCraft.Cdk.Constructs.Tests/LambdaFunctionConstructTests.cs index f931433..e5e5be2 100644 --- a/test/LayeredCraft.Cdk.Constructs.Tests/LambdaFunctionConstructTests.cs +++ b/test/LayeredCraft.Cdk.Constructs.Tests/LambdaFunctionConstructTests.cs @@ -155,7 +155,7 @@ public void Construct_ShouldIncludeOtelLayerWhenEnabled(LambdaFunctionConstructP template.HasResourceProperties("AWS::Lambda::Function", Match.ObjectLike(new Dictionary { { "TracingConfig", new Dictionary { { "Mode", "Active" } } }, - { "Layers", new object[] { Match.StringLikeRegexp(".*aws-otel-collector.*") } } + { "Layers", new object[] { Match.StringLikeRegexp(".*aws-otel-collector-amd64-ver-0-117-0.*") } } })); } diff --git a/test/LayeredCraft.Cdk.Constructs.Tests/TestKit/Attributes/LambdaFunctionConstructAutoDataAttribute.cs b/test/LayeredCraft.Cdk.Constructs.Tests/TestKit/Attributes/LambdaFunctionConstructAutoDataAttribute.cs index 4538782..aec6305 100644 --- a/test/LayeredCraft.Cdk.Constructs.Tests/TestKit/Attributes/LambdaFunctionConstructAutoDataAttribute.cs +++ b/test/LayeredCraft.Cdk.Constructs.Tests/TestKit/Attributes/LambdaFunctionConstructAutoDataAttribute.cs @@ -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) { diff --git a/test/LayeredCraft.Cdk.Constructs.Tests/TestKit/Customizations/LambdaFunctionConstructCustomization.cs b/test/LayeredCraft.Cdk.Constructs.Tests/TestKit/Customizations/LambdaFunctionConstructCustomization.cs index 4c9ad9f..d9c2fff 100644 --- a/test/LayeredCraft.Cdk.Constructs.Tests/TestKit/Customizations/LambdaFunctionConstructCustomization.cs +++ b/test/LayeredCraft.Cdk.Constructs.Tests/TestKit/Customizations/LambdaFunctionConstructCustomization.cs @@ -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) @@ -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()] : [])