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 @@ -14,7 +14,16 @@ namespace Aspire.Hosting;
/// </summary>
public static class AzureFunctionsProjectResourceExtensions
{
internal const string DefaultAzureFunctionsHostStorageName = "defaultazfuncstorage";
/// <remarks>
/// The prefix used for configuring the name default Azure Storage account that is used
/// for Azure Functions bookkeeping. Locally, the name is generated using a combination of this
/// prefix, a hash of the AppHost project name. During publish mode, the name generated
/// is a combination of this prefix and the name of the resource group associated with
/// the deployment. We want to keep the total number of characters in the name under
/// 24 characters to avoid truncation by Azure and allow
/// for unique enough identifiers.
/// </remarks>
internal const string DefaultAzureFunctionsHostStorageName = "funcstorage";

/// <summary>
/// Adds an Azure Functions project to the distributed application.
Expand All @@ -29,7 +38,7 @@ public static class AzureFunctionsProjectResourceExtensions

// Add the default storage resource if it doesn't already exist.
var storageResourceName = builder.CreateDefaultStorageName();
AzureStorageResource? storage = builder.Resources
var storage = builder.Resources
.OfType<AzureStorageResource>()
.FirstOrDefault(r => r.Name == storageResourceName);

Expand Down Expand Up @@ -198,6 +207,10 @@ public static IResourceBuilder<AzureFunctionsProjectResource> WithReference<TSou

private static string CreateDefaultStorageName(this IDistributedApplicationBuilder builder)
{
if (builder.ExecutionContext.IsPublishMode)
{
return DefaultAzureFunctionsHostStorageName;
}
var applicationHash = builder.Configuration["AppHost:Sha256"]![..5].ToLowerInvariant();
return $"{DefaultAzureFunctionsHostStorageName}{applicationHash}";
}
Expand Down
17 changes: 12 additions & 5 deletions src/Aspire.Hosting/DistributedApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,17 +151,13 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
var appHostName = options.ProjectName ?? _innerBuilder.Environment.ApplicationName;
AppHostPath = Path.Join(AppHostDirectory, appHostName);

var appHostShaBytes = SHA256.HashData(Encoding.UTF8.GetBytes(AppHostPath));
var appHostSha = Convert.ToHexString(appHostShaBytes);

// Set configuration
ConfigurePublishingOptions(options);
_innerBuilder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
// Make the app host directory available to the application via configuration
["AppHost:Directory"] = AppHostDirectory,
["AppHost:Path"] = AppHostPath,
["AppHost:Sha256"] = appHostSha,
});

_executionContextOptions = _innerBuilder.Configuration["Publishing:Publisher"] switch
Expand All @@ -172,13 +168,24 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)

ExecutionContext = new DistributedApplicationExecutionContext(_executionContextOptions);

//
Eventing.Subscribe<BeforeResourceStartedEvent>(async (@event, ct) =>
{
var rns = @event.Services.GetRequiredService<ResourceNotificationService>();
await rns.WaitForDependenciesAsync(@event.Resource, ct).ConfigureAwait(false);
});

// Conditionally configure AppHostSha based on execution context. For local scenarios, we want to
// account for the path the AppHost is running from to disambiguate between different projects
// with the same name as seen in https://github.com/dotnet/aspire/issues/5413. For publish scenarios,
// we want to use a stable hash based only on the project name.
var appHostShaBytes = SHA256.HashData(Encoding.UTF8.GetBytes(AppHostPath));
var appHostNameShaBytes = SHA256.HashData(Encoding.UTF8.GetBytes(appHostName));
var appHostSha = ExecutionContext.IsPublishMode ? Convert.ToHexString(appHostNameShaBytes) : Convert.ToHexString(appHostShaBytes);
_innerBuilder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["AppHost:Sha256"] = appHostSha
});

// Core things
_innerBuilder.Services.AddSingleton(sp => new DistributedApplicationModel(Resources));
_innerBuilder.Services.AddHostedService<DistributedApplicationLifecycle>();
Expand Down
11 changes: 11 additions & 0 deletions tests/Aspire.Hosting.Azure.Tests/AzureFunctionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ public async Task AddAzureFunctionsProject_WorksWithMultipleProjects()
await host.StopAsync();
}

[Fact]
public void AddAzureFunctionsProject_UsesCorrectNameUnderPublish()
{
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
builder.AddAzureFunctionsProject<TestProject>("funcapp");

var resource = Assert.Single(builder.Resources.OfType<AzureStorageResource>());

Assert.Equal(AzureFunctionsProjectResourceExtensions.DefaultAzureFunctionsHostStorageName, resource.Name);
}

private sealed class TestProject : IProjectMetadata
{
public string ProjectPath => "some-path";
Expand Down
21 changes: 21 additions & 0 deletions tests/Aspire.Hosting.Tests/Utils/VolumeNameGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using static Aspire.Hosting.Utils.VolumeNameGenerator;
using Xunit;
using Aspire.Hosting.Utils;

namespace Aspire.Hosting.Tests.Utils;

Expand Down Expand Up @@ -47,4 +48,24 @@ private sealed class TestResource(string name) : IResource

public ResourceAnnotationCollection Annotations { get; } = [];
}

[Fact]
public void VolumeNameDiffersBetweenPublishAndRun()
{
var runBuilder = TestDistributedApplicationBuilder.Create();
var publishBuilder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);

var runVolumePrefix = $"{Sanitize(runBuilder.Environment.ApplicationName).ToLowerInvariant()}-{runBuilder.Configuration["AppHost:Sha256"]!.ToLowerInvariant()[..10]}";
var publishVolumePrefix = $"{Sanitize(publishBuilder.Environment.ApplicationName).ToLowerInvariant()}-{publishBuilder.Configuration["AppHost:Sha256"]!.ToLowerInvariant()[..10]}";

var runResource = runBuilder.AddResource(new TestResource("myresource"));
var publishResource = publishBuilder.AddResource(new TestResource("myresource"));

var runVolumeName = CreateVolumeName(runResource, "data");
var publishVolumeName = CreateVolumeName(publishResource, "data");

Assert.Equal($"{runVolumePrefix}-{runResource.Resource.Name}-data", runVolumeName);
Assert.Equal($"{publishVolumePrefix}-{publishResource.Resource.Name}-data", publishVolumeName);
Assert.NotEqual(runVolumeName, publishVolumeName);
}
}