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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#pragma warning disable ASPIREACADOMAINS001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

using Aspire.Hosting.Azure;
using Azure.Provisioning.Storage;

var builder = DistributedApplication.CreateBuilder(args);
Expand Down Expand Up @@ -44,6 +45,13 @@
.WithReference(redis)
.WithReference(cosmosDb)
.WithEnvironment("VALUE", param)
.WithEnvironment(context =>
{
if (context.Resource.TryGetLastAnnotation<AppIdentityAnnotation>(out var identity))
{
context.EnvironmentVariables["AZURE_PRINCIPAL_NAME"] = identity.IdentityResource.PrincipalName;
}
})
.PublishAsAzureContainerApp((module, app) =>
{
app.ConfigureCustomDomain(customDomain, certificateName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ param account_kv_outputs_name string
@secure()
param secretparam_value string

param api_identity_outputs_principalname string

param infra_outputs_azure_container_apps_environment_id string

param infra_outputs_azure_container_registry_endpoint string
Expand Down Expand Up @@ -121,6 +123,10 @@ resource api 'Microsoft.App/containerApps@2024-03-01' = {
name: 'VALUE'
secretRef: 'value'
}
{
name: 'AZURE_PRINCIPAL_NAME'
value: api_identity_outputs_principalname
}
{
name: 'AZURE_CLIENT_ID'
value: api_identity_outputs_clientid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"cache_password_value": "{cache-password.value}",
"account_kv_outputs_name": "{account-kv.outputs.name}",
"secretparam_value": "{secretparam.value}",
"api_identity_outputs_principalname": "{api-identity.outputs.principalName}",
"infra_outputs_azure_container_apps_environment_id": "{infra.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_ID}",
"infra_outputs_azure_container_registry_endpoint": "{infra.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT}",
"infra_outputs_azure_container_registry_managed_identity_id": "{infra.outputs.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID}",
Expand All @@ -146,7 +147,8 @@
"ConnectionStrings__blobs": "{blobs.connectionString}",
"ConnectionStrings__cache": "{cache.connectionString}",
"ConnectionStrings__account": "{account.connectionString}",
"VALUE": "{secretparam.value}"
"VALUE": "{secretparam.value}",
"AZURE_PRINCIPAL_NAME": "{api-identity.outputs.principalName}"
},
"bindings": {
"http": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ private async Task ProcessEnvironmentAsync(DistributedApplicationExecutionContex
{
if (resource.TryGetAnnotationsOfType<EnvironmentCallbackAnnotation>(out var environmentCallbacks))
{
var context = new EnvironmentCallbackContext(executionContext, cancellationToken: cancellationToken);
var context = new EnvironmentCallbackContext(executionContext, resource, cancellationToken: cancellationToken);

foreach (var c in environmentCallbacks)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting.Azure/AzureResourcePreparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ private async Task<HashSet<IAzureResource>> GetAzureReferences(IResource resourc

if (resource.TryGetEnvironmentVariables(out var environmentCallbacks))
{
var context = new EnvironmentCallbackContext(executionContext, cancellationToken: cancellationToken);
var context = new EnvironmentCallbackContext(executionContext, resource, cancellationToken: cancellationToken);

foreach (var c in environmentCallbacks)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ private async Task ProcessEnvironmentAsync(DistributedApplicationExecutionContex
{
if (resource.TryGetAnnotationsOfType<EnvironmentCallbackAnnotation>(out var environmentCallbacks))
{
var context = new EnvironmentCallbackContext(executionContext, cancellationToken: cancellationToken);
var context = new EnvironmentCallbackContext(executionContext, resource, cancellationToken: cancellationToken);

foreach (var c in environmentCallbacks)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting.Kubernetes/KubernetesResourceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ private async Task ProcessEnvironmentAsync(DistributedApplicationExecutionContex
{
if (resource.TryGetAnnotationsOfType<EnvironmentCallbackAnnotation>(out var environmentCallbacks))
{
var context = new EnvironmentCallbackContext(executionContext, cancellationToken: cancellationToken);
var context = new EnvironmentCallbackContext(executionContext, resource, cancellationToken: cancellationToken);

foreach (var c in environmentCallbacks)
{
Expand Down
24 changes: 24 additions & 0 deletions src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ namespace Aspire.Hosting.ApplicationModel;
/// <param name="cancellationToken">A <see cref="CancellationToken"/>.</param>
public class EnvironmentCallbackContext(DistributedApplicationExecutionContext executionContext, Dictionary<string, object>? environmentVariables = null, CancellationToken cancellationToken = default)
{
private readonly IResource? _resource;

/// <summary>
/// Initializes a new instance of the <see cref="EnvironmentCallbackContext"/> class.
/// </summary>
/// <param name="executionContext">The execution context for this invocation of the AppHost.</param>
/// <param name="resource">The resource associated with this callback context.</param>
/// <param name="environmentVariables">The environment variables associated with this execution.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/>.</param>
public EnvironmentCallbackContext(DistributedApplicationExecutionContext executionContext, IResource resource, Dictionary<string, object>? environmentVariables = null, CancellationToken cancellationToken = default)
: this(executionContext, environmentVariables, cancellationToken)
{
_resource = resource ?? throw new ArgumentNullException(nameof(resource));
}

/// <summary>
/// Gets the environment variables associated with the callback context.
/// </summary>
Expand All @@ -29,6 +44,15 @@ public class EnvironmentCallbackContext(DistributedApplicationExecutionContext e
/// </summary>
public ILogger Logger { get; set; } = NullLogger.Instance;

/// <summary>
/// The resource associated with this callback context.
/// </summary>
/// <remarks>
/// This will be set to the resource in all cases where .NET Aspire invokes the callback.
/// </remarks>
/// <exception cref="InvalidOperationException">Thrown when the EnvironmentCallbackContext was created without a specified resource.</exception>
public IResource Resource => _resource ?? throw new InvalidOperationException($"{nameof(Resource)} is not set. This callback context is not associated with a resource.");

/// <summary>
/// Gets the execution context associated with this invocation of the AppHost.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public static async ValueTask ProcessEnvironmentVariableValuesAsync(
if (resource.TryGetEnvironmentVariables(out var callbacks))
{
var config = new Dictionary<string, object>();
var context = new EnvironmentCallbackContext(executionContext, config, cancellationToken)
var context = new EnvironmentCallbackContext(executionContext, resource, config, cancellationToken)
{
Logger = logger
};
Expand Down
2 changes: 2 additions & 0 deletions tests/Aspire.Hosting.Tests/ExpressionResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ public async Task HostUrlPropertyGetsResolved(bool container, string hostUrlVal,
var test = builder.AddResource(new ContainerResource("testSource"))
.WithEnvironment(env =>
{
Assert.NotNull(env.Resource);

env.EnvironmentVariables["envname"] = new HostUrl(hostUrlVal);
});

Expand Down
2 changes: 2 additions & 0 deletions tests/Aspire.Hosting.Tests/ResourceExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ public async Task GetEnvironmentVariableValuesAsyncReturnCorrectVariablesInRunMo
.WithEnvironment("xpack.security.enabled", "true")
.WithEnvironment(context =>
{
Assert.NotNull(context.Resource);

context.EnvironmentVariables["ELASTIC_PASSWORD"] = "p@ssw0rd1";
});

Expand Down
28 changes: 28 additions & 0 deletions tests/Aspire.Hosting.Tests/WithEnvironmentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public async Task BuiltApplicationHasAccessToIServiceProviderViaEnvironmentCallb
var container = builder.AddContainer("container", "image")
.WithEnvironment(context =>
{
Assert.NotNull(context.Resource);

var sp = context.ExecutionContext.ServiceProvider;
context.EnvironmentVariables["SP_AVAILABLE"] = sp is not null ? "true" : "false";
});
Expand Down Expand Up @@ -161,6 +163,32 @@ public async Task ComplexEnvironmentCallbackPopulatesValueWhenCalled()
var projectA = builder.AddProject<ProjectA>("projectA")
.WithEnvironment(context =>
{
Assert.NotNull(context.Resource);

context.EnvironmentVariables["myName"] = environmentValue;
});

environmentValue = "value2";

// Call environment variable callbacks.
var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(projectA.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout();

Assert.Equal("value2", config["myName"]);
}

[Fact]
public async Task ComplexAsyncEnvironmentCallbackPopulatesValueWhenCalled()
{
using var builder = TestDistributedApplicationBuilder.Create();

var environmentValue = "value";
var projectA = builder.AddProject<ProjectA>("projectA")
.WithEnvironment(async context =>
{
await Task.Yield();

Assert.NotNull(context.Resource);

context.EnvironmentVariables["myName"] = environmentValue;
});

Expand Down