1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using System . Text ;
4
5
using System . Text . Json ;
5
6
using System . Text . Json . Nodes ;
6
7
using Aspire . Hosting ;
9
10
using Aspire . Hosting . Azure . EventHubs ;
10
11
using Azure . Provisioning ;
11
12
using Azure . Provisioning . EventHubs ;
12
- using Microsoft . Extensions . DependencyInjection ;
13
13
using AzureProvisioning = Azure . Provisioning . EventHubs ;
14
14
15
15
namespace Aspire . Hosting ;
@@ -19,8 +19,6 @@ namespace Aspire.Hosting;
19
19
/// </summary>
20
20
public static class AzureEventHubsExtensions
21
21
{
22
- private const UnixFileMode FileMode644 = UnixFileMode . UserRead | UnixFileMode . UserWrite | UnixFileMode . GroupRead | UnixFileMode . OtherRead ;
23
-
24
22
private const string EmulatorHealthEndpointName = "emulatorhealth" ;
25
23
26
24
/// <summary>
@@ -31,7 +29,7 @@ public static class AzureEventHubsExtensions
31
29
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
32
30
/// <remarks>
33
31
/// By default references to the Azure AppEvent Hubs Namespace resource will be assigned the following roles:
34
- ///
32
+ ///
35
33
/// - <see cref="EventHubsBuiltInRole.AzureEventHubsDataOwner"/>
36
34
///
37
35
/// 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
240
238
var lifetime = ContainerLifetime . Session ;
241
239
242
240
// 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 ) ;
244
243
if ( configureContainer != null )
245
244
{
246
- var surrogate = new AzureEventHubsEmulatorResource ( builder . Resource ) ;
247
- var surrogateBuilder = builder . ApplicationBuilder . CreateResourceBuilder ( surrogate ) ;
248
245
configureContainer ( surrogateBuilder ) ;
249
246
250
247
if ( surrogate . TryGetLastAnnotation < ContainerLifetimeAnnotation > ( out var lifetimeAnnotation ) )
@@ -269,77 +266,55 @@ public static IResourceBuilder<AzureEventHubsResource> RunAsEmulator(this IResou
269
266
270
267
// RunAsEmulator() can be followed by custom model configuration so we need to delay the creation of the Config.json file
271
268
// 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
+ ( _ , _ ) =>
280
272
{
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
+ }
283
284
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
+ }
286
292
287
- try
288
- {
289
293
// Apply ConfigJsonAnnotation modifications
290
294
var configJsonAnnotations = builder . Resource . Annotations . OfType < ConfigJsonAnnotation > ( ) ;
291
295
292
296
if ( configJsonAnnotations . Any ( ) )
293
297
{
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
-
303
298
foreach ( var annotation in configJsonAnnotations )
304
299
{
305
- annotation . Configure ( jsonObject ) ;
300
+ annotation . Configure ( tempConfig ) ;
306
301
}
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 ) ;
311
302
}
312
303
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 ) ;
323
307
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 ( ) ;
340
309
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
+ } ) ;
343
318
344
319
return builder ;
345
320
}
@@ -413,14 +388,7 @@ public static IResourceBuilder<AzureEventHubsEmulatorResource> WithConfiguration
413
388
ArgumentNullException . ThrowIfNull ( builder ) ;
414
389
ArgumentException . ThrowIfNullOrEmpty ( path ) ;
415
390
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 ) ;
424
392
}
425
393
426
394
/// <summary>
@@ -439,12 +407,9 @@ public static IResourceBuilder<AzureEventHubsEmulatorResource> WithConfiguration
439
407
return builder ;
440
408
}
441
409
442
- private static string WriteEmulatorConfigJson ( AzureEventHubsResource emulatorResource )
410
+ private static string CreateEmulatorConfigJson ( AzureEventHubsResource emulatorResource )
443
411
{
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 ( ) ;
448
413
using var writer = new Utf8JsonWriter ( stream , new JsonWriterOptions { Indented = true } ) ;
449
414
450
415
writer . WriteStartObject ( ) ; // {
@@ -474,7 +439,9 @@ private static string WriteEmulatorConfigJson(AzureEventHubsResource emulatorRes
474
439
writer . WriteEndObject ( ) ; // } (/UserConfig)
475
440
writer . WriteEndObject ( ) ; // } (/Root)
476
441
477
- return filePath ;
442
+ writer . Flush ( ) ;
443
+
444
+ return Encoding . UTF8 . GetString ( stream . ToArray ( ) ) ;
478
445
}
479
446
480
447
/// <summary>
@@ -491,7 +458,7 @@ private static string WriteEmulatorConfigJson(AzureEventHubsResource emulatorRes
491
458
/// var builder = DistributedApplication.CreateBuilder(args);
492
459
///
493
460
/// var eventHubs = builder.AddAzureEventHubs("eventHubs");
494
- ///
461
+ ///
495
462
/// var api = builder.AddProject<Projects.Api>("api")
496
463
/// .WithRoleAssignments(eventHubs, EventHubsBuiltInRole.AzureEventHubsDataSender)
497
464
/// .WithReference(eventHubs);
0 commit comments