11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4+ using System . Text ;
45using System . Text . Json ;
56using System . Text . Json . Nodes ;
67using Aspire . Hosting ;
910using Aspire . Hosting . Azure . EventHubs ;
1011using Azure . Provisioning ;
1112using Azure . Provisioning . EventHubs ;
12- using Microsoft . Extensions . DependencyInjection ;
1313using AzureProvisioning = Azure . Provisioning . EventHubs ;
1414
1515namespace Aspire . Hosting ;
@@ -19,8 +19,6 @@ namespace Aspire.Hosting;
1919/// </summary>
2020public static class AzureEventHubsExtensions
2121{
22- private const UnixFileMode FileMode644 = UnixFileMode . UserRead | UnixFileMode . UserWrite | UnixFileMode . GroupRead | UnixFileMode . OtherRead ;
23-
2422 private const string EmulatorHealthEndpointName = "emulatorhealth" ;
2523
2624 /// <summary>
@@ -31,7 +29,7 @@ public static class AzureEventHubsExtensions
3129 /// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
3230 /// <remarks>
3331 /// By default references to the Azure AppEvent Hubs Namespace resource will be assigned the following roles:
34- ///
32+ ///
3533 /// - <see cref="EventHubsBuiltInRole.AzureEventHubsDataOwner"/>
3634 ///
3735 /// These can be replaced by calling <see cref="WithRoleAssignments{T}(IResourceBuilder{T}, IResourceBuilder{AzureEventHubsResource}, EventHubsBuiltInRole[])"/>.
@@ -240,11 +238,10 @@ public static IResourceBuilder<AzureEventHubsResource> RunAsEmulator(this IResou
240238 var lifetime = ContainerLifetime . Session ;
241239
242240 // Copy the lifetime from the main resource to the storage resource
243-
241+ var surrogate = new AzureEventHubsEmulatorResource ( builder . Resource ) ;
242+ var surrogateBuilder = builder . ApplicationBuilder . CreateResourceBuilder ( surrogate ) ;
244243 if ( configureContainer != null )
245244 {
246- var surrogate = new AzureEventHubsEmulatorResource ( builder . Resource ) ;
247- var surrogateBuilder = builder . ApplicationBuilder . CreateResourceBuilder ( surrogate ) ;
248245 configureContainer ( surrogateBuilder ) ;
249246
250247 if ( surrogate . TryGetLastAnnotation < ContainerLifetimeAnnotation > ( out var lifetimeAnnotation ) )
@@ -269,77 +266,55 @@ public static IResourceBuilder<AzureEventHubsResource> RunAsEmulator(this IResou
269266
270267 // RunAsEmulator() can be followed by custom model configuration so we need to delay the creation of the Config.json file
271268 // until all resources are about to be prepared and annotations can't be updated anymore.
272-
273- builder . ApplicationBuilder . Eventing . Subscribe < BeforeStartEvent > ( ( @event , ct ) =>
274- {
275- // Create JSON configuration file
276-
277- var hasCustomConfigJson = builder . Resource . Annotations . OfType < ContainerMountAnnotation > ( ) . Any ( v => v . Target == AzureEventHubsEmulatorResource . EmulatorConfigJsonPath ) ;
278-
279- if ( hasCustomConfigJson )
269+ surrogateBuilder . WithContainerFiles (
270+ AzureEventHubsEmulatorResource . EmulatorConfigFilesPath ,
271+ ( _ , _ ) =>
280272 {
281- return Task . CompletedTask ;
282- }
273+ var customConfigFile = builder . Resource . Annotations . OfType < ConfigFileAnnotation > ( ) . FirstOrDefault ( ) ;
274+ if ( customConfigFile != null )
275+ {
276+ return Task . FromResult < IEnumerable < ContainerFileSystemItem > > ( [
277+ new ContainerFile
278+ {
279+ Name = AzureEventHubsEmulatorResource . EmulatorConfigJsonFile ,
280+ SourcePath = customConfigFile . SourcePath ,
281+ } ,
282+ ] ) ;
283+ }
283284
284- // Create Config.json file content and its alterations in a temporary file
285- var tempConfigFile = WriteEmulatorConfigJson ( builder . Resource ) ;
285+ // Create default Config.json file content
286+ var tempConfig = JsonNode . Parse ( CreateEmulatorConfigJson ( builder . Resource ) ) ;
287+
288+ if ( tempConfig == null )
289+ {
290+ throw new InvalidOperationException ( "The configuration file mount could not be parsed." ) ;
291+ }
286292
287- try
288- {
289293 // Apply ConfigJsonAnnotation modifications
290294 var configJsonAnnotations = builder . Resource . Annotations . OfType < ConfigJsonAnnotation > ( ) ;
291295
292296 if ( configJsonAnnotations . Any ( ) )
293297 {
294- using var readStream = new FileStream ( tempConfigFile , FileMode . Open , FileAccess . Read ) ;
295- var jsonObject = JsonNode . Parse ( readStream ) ;
296- readStream . Close ( ) ;
297-
298- if ( jsonObject == null )
299- {
300- throw new InvalidOperationException ( "The configuration file mount could not be parsed." ) ;
301- }
302-
303298 foreach ( var annotation in configJsonAnnotations )
304299 {
305- annotation . Configure ( jsonObject ) ;
300+ annotation . Configure ( tempConfig ) ;
306301 }
307-
308- using var writeStream = new FileStream ( tempConfigFile , FileMode . Open , FileAccess . Write ) ;
309- using var writer = new Utf8JsonWriter ( writeStream , new JsonWriterOptions { Indented = true } ) ;
310- jsonObject . WriteTo ( writer ) ;
311302 }
312303
313- var aspireStore = @event . Services . GetRequiredService < IAspireStore > ( ) ;
314-
315- // Deterministic file path for the configuration file based on its content
316- var configJsonPath = aspireStore . GetFileNameWithContent ( $ "{ builder . Resource . Name } -Config.json", tempConfigFile ) ;
317-
318- // The docker container runs as a non-root user, so we need to grant other user's read/write permission
319- if ( ! OperatingSystem . IsWindows ( ) )
320- {
321- File . SetUnixFileMode ( configJsonPath , FileMode644 ) ;
322- }
304+ using var writeStream = new MemoryStream ( ) ;
305+ using var writer = new Utf8JsonWriter ( writeStream , new JsonWriterOptions { Indented = true } ) ;
306+ tempConfig . WriteTo ( writer ) ;
323307
324- builder . WithAnnotation ( new ContainerMountAnnotation (
325- configJsonPath ,
326- AzureEventHubsEmulatorResource . EmulatorConfigJsonPath ,
327- ContainerMountType . BindMount ,
328- isReadOnly : true ) ) ;
329- }
330- finally
331- {
332- try
333- {
334- File . Delete ( tempConfigFile ) ;
335- }
336- catch
337- {
338- }
339- }
308+ writer . Flush ( ) ;
340309
341- return Task . CompletedTask ;
342- } ) ;
310+ return Task . FromResult < IEnumerable < ContainerFileSystemItem > > ( [
311+ new ContainerFile
312+ {
313+ Name = AzureEventHubsEmulatorResource . EmulatorConfigJsonFile ,
314+ Contents = Encoding . UTF8 . GetString ( writeStream . ToArray ( ) ) ,
315+ } ,
316+ ] ) ;
317+ } ) ;
343318
344319 return builder ;
345320 }
@@ -413,14 +388,7 @@ public static IResourceBuilder<AzureEventHubsEmulatorResource> WithConfiguration
413388 ArgumentNullException . ThrowIfNull ( builder ) ;
414389 ArgumentException . ThrowIfNullOrEmpty ( path ) ;
415390
416- // Update the existing mount
417- var configFileMount = builder . Resource . Annotations . OfType < ContainerMountAnnotation > ( ) . LastOrDefault ( v => v . Target == AzureEventHubsEmulatorResource . EmulatorConfigJsonPath ) ;
418- if ( configFileMount != null )
419- {
420- builder . Resource . Annotations . Remove ( configFileMount ) ;
421- }
422-
423- return builder . WithBindMount ( path , AzureEventHubsEmulatorResource . EmulatorConfigJsonPath , isReadOnly : true ) ;
391+ return builder . WithAnnotation ( new ConfigFileAnnotation ( path ) , ResourceAnnotationMutationBehavior . Replace ) ;
424392 }
425393
426394 /// <summary>
@@ -439,12 +407,9 @@ public static IResourceBuilder<AzureEventHubsEmulatorResource> WithConfiguration
439407 return builder ;
440408 }
441409
442- private static string WriteEmulatorConfigJson ( AzureEventHubsResource emulatorResource )
410+ private static string CreateEmulatorConfigJson ( AzureEventHubsResource emulatorResource )
443411 {
444- // This temporary file is not used by the container, it will be copied and then deleted
445- var filePath = Path . GetTempFileName ( ) ;
446-
447- using var stream = new FileStream ( filePath , FileMode . Open , FileAccess . Write ) ;
412+ using var stream = new MemoryStream ( ) ;
448413 using var writer = new Utf8JsonWriter ( stream , new JsonWriterOptions { Indented = true } ) ;
449414
450415 writer . WriteStartObject ( ) ; // {
@@ -474,7 +439,9 @@ private static string WriteEmulatorConfigJson(AzureEventHubsResource emulatorRes
474439 writer . WriteEndObject ( ) ; // } (/UserConfig)
475440 writer . WriteEndObject ( ) ; // } (/Root)
476441
477- return filePath ;
442+ writer . Flush ( ) ;
443+
444+ return Encoding . UTF8 . GetString ( stream . ToArray ( ) ) ;
478445 }
479446
480447 /// <summary>
@@ -491,7 +458,7 @@ private static string WriteEmulatorConfigJson(AzureEventHubsResource emulatorRes
491458 /// var builder = DistributedApplication.CreateBuilder(args);
492459 ///
493460 /// var eventHubs = builder.AddAzureEventHubs("eventHubs");
494- ///
461+ ///
495462 /// var api = builder.AddProject<Projects.Api>("api")
496463 /// .WithRoleAssignments(eventHubs, EventHubsBuiltInRole.AzureEventHubsDataSender)
497464 /// .WithReference(eventHubs);
0 commit comments