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
117 changes: 117 additions & 0 deletions docs/utilities/metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
title: Lambda Metadata
description: Utility
---

<!-- markdownlint-disable MD013 -->
The Lambda Metadata utility provides access to the Lambda Metadata Endpoint (LMDS), giving you execution environment metadata like Availability Zone ID.

## Key features

* Retrieve Lambda execution environment metadata
* Automatic caching for the sandbox lifetime
* Thread-safe access
* Native AOT compatible

## Installation

```bash
dotnet add package AWS.Lambda.Powertools.Metadata
```

## Getting started

```csharp
using AWS.Lambda.Powertools.Metadata;

public class Function
{
public string Handler(object input, ILambdaContext context)
{
var azId = LambdaMetadata.AvailabilityZoneId;
return $"Running in AZ: {azId}";
}
}
```

## Available metadata

| Property | Type | Description |
|-----------------------|-----------|--------------------------------------------------------------------------|
| `AvailabilityZoneId` | `string?` | The AZ where the function is running (e.g., `use1-az1`), or `null` when unavailable |

## Error handling

```csharp
using AWS.Lambda.Powertools.Metadata;
using AWS.Lambda.Powertools.Metadata.Exceptions;

try
{
var azId = LambdaMetadata.AvailabilityZoneId;
}
catch (LambdaMetadataException ex)
{
Console.WriteLine($"Failed to get metadata: {ex.Message}");

if (ex.StatusCode != -1)
Console.WriteLine($"HTTP Status: {ex.StatusCode}");
}
```

## Refreshing metadata

Metadata remains constant for the Lambda sandbox lifetime. If you need to force a refresh:

```csharp
LambdaMetadata.Refresh();
```

## Thread safety

`LambdaMetadata.AvailabilityZoneId` is thread-safe. You can access it from multiple concurrent invocations without race conditions.

## Use cases

### Multi-AZ routing

```csharp
using AWS.Lambda.Powertools.Metadata;

public class Function
{
public async Task<string> Handler(OrderRequest request, ILambdaContext context)
{
var endpoint = LambdaMetadata.AvailabilityZoneId switch
{
"use1-az1" => "https://service-az1.internal",
"use1-az2" => "https://service-az2.internal",
_ => "https://service.internal"
};

return await ProcessOrder(request, endpoint);
}
}
```

### Logging

```csharp
using AWS.Lambda.Powertools.Logging;
using AWS.Lambda.Powertools.Metadata;

public class Function
{
public Function()
{
Logger.AppendKey("az_id", LambdaMetadata.AvailabilityZoneId);
}

[Logging]
public string Handler(object input, ILambdaContext context)
{
Logger.LogInformation("Processing request");
return "Success";
}
}
```
1,592 changes: 809 additions & 783 deletions libraries/AWS.Lambda.Powertools.sln

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<!-- Remaining properties are defined in Directory.Build.props -->
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>AWS.Lambda.Powertools.Metadata</PackageId>
<Description>Powertools for AWS Lambda (.NET) - Lambda Metadata package. Provides access to Lambda execution environment metadata from the Lambda Metadata Endpoint (LMDS).</Description>
<AssemblyName>AWS.Lambda.Powertools.Metadata</AssemblyName>
<RootNamespace>AWS.Lambda.Powertools.Metadata</RootNamespace>
<IncludeCommonFiles>true</IncludeCommonFiles>
</PropertyGroup>

<ItemGroup>
<!-- Package versions are Centrally managed in Directory.Packages.props file -->
<!-- More info https://learn.microsoft.com/en-us/nuget/consume-packages/central-package-management -->
<PackageReference Include="Amazon.Lambda.Core"/>
<ProjectReference Include="..\AWS.Lambda.Powertools.Common\AWS.Lambda.Powertools.Common.csproj" Condition="'$(Configuration)'=='Debug'"/>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

namespace AWS.Lambda.Powertools.Metadata.Exceptions;

/// <summary>
/// Exception thrown when the Lambda Metadata Endpoint is unavailable or returns an error.
/// <para>
/// This exception may be thrown when:
/// <list type="bullet">
/// <item><description>The metadata endpoint environment variables are not set</description></item>
/// <item><description>The metadata endpoint returns a non-200 status code</description></item>
/// <item><description>Network errors occur when connecting to the endpoint</description></item>
/// <item><description>The response cannot be parsed</description></item>
/// </list>
/// </para>
/// </summary>
public class LambdaMetadataException : Exception
{
/// <summary>
/// Gets the HTTP status code from the metadata endpoint.
/// Returns -1 if not applicable.
/// </summary>
public int StatusCode { get; }

/// <summary>
/// Constructs a new exception with the specified message.
/// </summary>
/// <param name="message">The error message.</param>
public LambdaMetadataException(string message) : base(message)
{
StatusCode = -1;
}

/// <summary>
/// Constructs a new exception with the specified message and cause.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="innerException">The underlying cause.</param>
public LambdaMetadataException(string message, Exception innerException) : base(message, innerException)
{
StatusCode = -1;
}

/// <summary>
/// Constructs a new exception with the specified message and HTTP status code.
/// </summary>
/// <param name="message">The error message.</param>
/// <param name="statusCode">The HTTP status code from the metadata endpoint.</param>
public LambdaMetadataException(string message, int statusCode) : base(message)
{
StatusCode = statusCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

namespace AWS.Lambda.Powertools.Metadata.Internal;

/// <summary>
/// Internal interface for fetching Lambda metadata.
/// </summary>
internal interface IMetadataFetcher
{
/// <summary>
/// Fetches metadata from the Lambda Metadata Endpoint.
/// </summary>
MetadataValues Fetch();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

using System.Text.Json.Serialization;

namespace AWS.Lambda.Powertools.Metadata.Internal;

/// <summary>
/// Source-generated JSON serializer context for AOT compatibility.
/// </summary>
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.Unspecified,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false)]
[JsonSerializable(typeof(MetadataValues))]
internal partial class LambdaMetadataSerializerContext : JsonSerializerContext
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

using System.Net;
using System.Text.Json;
using AWS.Lambda.Powertools.Metadata.Exceptions;

namespace AWS.Lambda.Powertools.Metadata.Internal;

/// <summary>
/// Fetches metadata from the Lambda Metadata Endpoint (LMDS).
/// </summary>
internal sealed class MetadataFetcher : IMetadataFetcher
{
private const string EnvMetadataApi = "AWS_LAMBDA_METADATA_API";
private const string EnvMetadataToken = "AWS_LAMBDA_METADATA_TOKEN";
private const string ApiVersion = "2026-01-15";
private const string MetadataPath = "/metadata/execution-environment";

private readonly HttpClient _httpClient;

public MetadataFetcher() : this(CreateHttpClient())
{
}

internal MetadataFetcher(HttpClient httpClient)
{
_httpClient = httpClient;
}

private static HttpClient CreateHttpClient()
{
return new HttpClient(new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.None
})
{
Timeout = TimeSpan.FromSeconds(1)
};
}

public MetadataValues Fetch()
{
var (token, url) = GetEndpointInfo();

try
{
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Authorization", $"Bearer {token}");

using var response = _httpClient.Send(request);
return ProcessResponse(response);
}
catch (LambdaMetadataException)
{
throw;
}
catch (Exception ex)
{
throw new LambdaMetadataException($"Failed to fetch Lambda metadata: {ex.Message}", ex);
}
}

private static (string token, string url) GetEndpointInfo()
{
var token = Environment.GetEnvironmentVariable(EnvMetadataToken);
var api = Environment.GetEnvironmentVariable(EnvMetadataApi);

if (string.IsNullOrEmpty(token))
throw new LambdaMetadataException(
$"Lambda metadata token not available. Ensure {EnvMetadataToken} is set.");

if (string.IsNullOrEmpty(api))
throw new LambdaMetadataException(
$"Lambda metadata API endpoint not available. Ensure {EnvMetadataApi} is set.");

return (token, $"http://{api}/{ApiVersion}{MetadataPath}");
}

private static MetadataValues ProcessResponse(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)
{
var error = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
throw new LambdaMetadataException(
$"Metadata request failed with status {(int)response.StatusCode}: {error}",
(int)response.StatusCode);
}

var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return JsonSerializer.Deserialize(body, LambdaMetadataSerializerContext.Default.MetadataValues)
?? throw new LambdaMetadataException("Failed to deserialize Lambda metadata response.");
}
}
20 changes: 20 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Metadata/InternalsVisibleTo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metadata.Tests")]
[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.ConcurrencyTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
Loading
Loading