Skip to content

Commit 803a122

Browse files
authored
Shorten Functions storage prefix and use AppHost name SHA (#6301)
* Shorten Functions storage prefix and use AppHost name SHA * Use name only when IsPublishMode == true * Add tests and change Functions logic * Update doc string
1 parent 042a8ed commit 803a122

File tree

4 files changed

+59
-7
lines changed

4 files changed

+59
-7
lines changed

src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,16 @@ namespace Aspire.Hosting;
1414
/// </summary>
1515
public static class AzureFunctionsProjectResourceExtensions
1616
{
17-
internal const string DefaultAzureFunctionsHostStorageName = "defaultazfuncstorage";
17+
/// <remarks>
18+
/// The prefix used for configuring the name default Azure Storage account that is used
19+
/// for Azure Functions bookkeeping. Locally, the name is generated using a combination of this
20+
/// prefix, a hash of the AppHost project name. During publish mode, the name generated
21+
/// is a combination of this prefix and the name of the resource group associated with
22+
/// the deployment. We want to keep the total number of characters in the name under
23+
/// 24 characters to avoid truncation by Azure and allow
24+
/// for unique enough identifiers.
25+
/// </remarks>
26+
internal const string DefaultAzureFunctionsHostStorageName = "funcstorage";
1827

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

3039
// Add the default storage resource if it doesn't already exist.
3140
var storageResourceName = builder.CreateDefaultStorageName();
32-
AzureStorageResource? storage = builder.Resources
41+
var storage = builder.Resources
3342
.OfType<AzureStorageResource>()
3443
.FirstOrDefault(r => r.Name == storageResourceName);
3544

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

199208
private static string CreateDefaultStorageName(this IDistributedApplicationBuilder builder)
200209
{
210+
if (builder.ExecutionContext.IsPublishMode)
211+
{
212+
return DefaultAzureFunctionsHostStorageName;
213+
}
201214
var applicationHash = builder.Configuration["AppHost:Sha256"]![..5].ToLowerInvariant();
202215
return $"{DefaultAzureFunctionsHostStorageName}{applicationHash}";
203216
}

src/Aspire.Hosting/DistributedApplicationBuilder.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,17 +151,13 @@ public DistributedApplicationBuilder(DistributedApplicationOptions options)
151151
var appHostName = options.ProjectName ?? _innerBuilder.Environment.ApplicationName;
152152
AppHostPath = Path.Join(AppHostDirectory, appHostName);
153153

154-
var appHostShaBytes = SHA256.HashData(Encoding.UTF8.GetBytes(AppHostPath));
155-
var appHostSha = Convert.ToHexString(appHostShaBytes);
156-
157154
// Set configuration
158155
ConfigurePublishingOptions(options);
159156
_innerBuilder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
160157
{
161158
// Make the app host directory available to the application via configuration
162159
["AppHost:Directory"] = AppHostDirectory,
163160
["AppHost:Path"] = AppHostPath,
164-
["AppHost:Sha256"] = appHostSha,
165161
});
166162

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

173169
ExecutionContext = new DistributedApplicationExecutionContext(_executionContextOptions);
174170

175-
//
176171
Eventing.Subscribe<BeforeResourceStartedEvent>(async (@event, ct) =>
177172
{
178173
var rns = @event.Services.GetRequiredService<ResourceNotificationService>();
179174
await rns.WaitForDependenciesAsync(@event.Resource, ct).ConfigureAwait(false);
180175
});
181176

177+
// Conditionally configure AppHostSha based on execution context. For local scenarios, we want to
178+
// account for the path the AppHost is running from to disambiguate between different projects
179+
// with the same name as seen in https://github.com/dotnet/aspire/issues/5413. For publish scenarios,
180+
// we want to use a stable hash based only on the project name.
181+
var appHostShaBytes = SHA256.HashData(Encoding.UTF8.GetBytes(AppHostPath));
182+
var appHostNameShaBytes = SHA256.HashData(Encoding.UTF8.GetBytes(appHostName));
183+
var appHostSha = ExecutionContext.IsPublishMode ? Convert.ToHexString(appHostNameShaBytes) : Convert.ToHexString(appHostShaBytes);
184+
_innerBuilder.Configuration.AddInMemoryCollection(new Dictionary<string, string?>
185+
{
186+
["AppHost:Sha256"] = appHostSha
187+
});
188+
182189
// Core things
183190
_innerBuilder.Services.AddSingleton(sp => new DistributedApplicationModel(Resources));
184191
_innerBuilder.Services.AddHostedService<DistributedApplicationLifecycle>();

tests/Aspire.Hosting.Azure.Tests/AzureFunctionsTests.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,17 @@ public async Task AddAzureFunctionsProject_WorksWithMultipleProjects()
149149
await host.StopAsync();
150150
}
151151

152+
[Fact]
153+
public void AddAzureFunctionsProject_UsesCorrectNameUnderPublish()
154+
{
155+
using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
156+
builder.AddAzureFunctionsProject<TestProject>("funcapp");
157+
158+
var resource = Assert.Single(builder.Resources.OfType<AzureStorageResource>());
159+
160+
Assert.Equal(AzureFunctionsProjectResourceExtensions.DefaultAzureFunctionsHostStorageName, resource.Name);
161+
}
162+
152163
private sealed class TestProject : IProjectMetadata
153164
{
154165
public string ProjectPath => "some-path";

tests/Aspire.Hosting.Tests/Utils/VolumeNameGeneratorTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using static Aspire.Hosting.Utils.VolumeNameGenerator;
55
using Xunit;
6+
using Aspire.Hosting.Utils;
67

78
namespace Aspire.Hosting.Tests.Utils;
89

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

4849
public ResourceAnnotationCollection Annotations { get; } = [];
4950
}
51+
52+
[Fact]
53+
public void VolumeNameDiffersBetweenPublishAndRun()
54+
{
55+
var runBuilder = TestDistributedApplicationBuilder.Create();
56+
var publishBuilder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
57+
58+
var runVolumePrefix = $"{Sanitize(runBuilder.Environment.ApplicationName).ToLowerInvariant()}-{runBuilder.Configuration["AppHost:Sha256"]!.ToLowerInvariant()[..10]}";
59+
var publishVolumePrefix = $"{Sanitize(publishBuilder.Environment.ApplicationName).ToLowerInvariant()}-{publishBuilder.Configuration["AppHost:Sha256"]!.ToLowerInvariant()[..10]}";
60+
61+
var runResource = runBuilder.AddResource(new TestResource("myresource"));
62+
var publishResource = publishBuilder.AddResource(new TestResource("myresource"));
63+
64+
var runVolumeName = CreateVolumeName(runResource, "data");
65+
var publishVolumeName = CreateVolumeName(publishResource, "data");
66+
67+
Assert.Equal($"{runVolumePrefix}-{runResource.Resource.Name}-data", runVolumeName);
68+
Assert.Equal($"{publishVolumePrefix}-{publishResource.Resource.Name}-data", publishVolumeName);
69+
Assert.NotEqual(runVolumeName, publishVolumeName);
70+
}
5071
}

0 commit comments

Comments
 (0)