Skip to content

Commit 242a053

Browse files
authored
Add support for copying existing files via WithContainerFiles API (#8908)
* New DCP create files schema * Update keycloak realm init to use WithContainerFiles * Update tests, add helpers, cover additional scenarios * Update more usage of BindMounts for config, make old methods Obsolete * Don't update generated files * Don't change isReadOnly * Update another init bind mount usage to WithContainerFiles * Add updated test file * Remove unused usings * Add support for WithContainerFiles attributes in docker compose publish output * Avoid using a path that Verifier will scrub * Copy WithContainerFiles source files as part of publish * uid and gid are strings * Ensure configs soure paths are written unix style for portability
1 parent e7b4e7e commit 242a053

File tree

38 files changed

+1211
-294
lines changed

38 files changed

+1211
-294
lines changed

src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsEmulatorResource.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ public class AzureEventHubsEmulatorResource(AzureEventHubsResource innerResource
1313
: ContainerResource(innerResource.Name), IResource
1414
{
1515
// The path to the emulator configuration file in the container.
16-
internal const string EmulatorConfigJsonPath = "/Eventhubs_Emulator/ConfigFiles/Config.json";
16+
// The path to the emulator configuration files in the container.
17+
internal const string EmulatorConfigFilesPath = "/Eventhubs_Emulator/ConfigFiles";
18+
// The path to the emulator configuration file in the container.
19+
internal const string EmulatorConfigJsonFile = "Config.json";
1720

1821
private readonly AzureEventHubsResource _innerResource = innerResource ?? throw new ArgumentNullException(nameof(innerResource));
1922

src/Aspire.Hosting.Azure.EventHubs/AzureEventHubsExtensions.cs

Lines changed: 45 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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;
45
using System.Text.Json;
56
using System.Text.Json.Nodes;
67
using Aspire.Hosting;
@@ -9,7 +10,6 @@
910
using Aspire.Hosting.Azure.EventHubs;
1011
using Azure.Provisioning;
1112
using Azure.Provisioning.EventHubs;
12-
using Microsoft.Extensions.DependencyInjection;
1313
using AzureProvisioning = Azure.Provisioning.EventHubs;
1414

1515
namespace Aspire.Hosting;
@@ -19,8 +19,6 @@ namespace Aspire.Hosting;
1919
/// </summary>
2020
public 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&lt;Projects.Api&gt;("api")
496463
/// .WithRoleAssignments(eventHubs, EventHubsBuiltInRole.AzureEventHubsDataSender)
497464
/// .WithReference(eventHubs);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Hosting.ApplicationModel;
5+
6+
namespace Aspire.Hosting.Azure.EventHubs;
7+
8+
/// <summary>
9+
/// Represents an annotation for a custom config file source.
10+
/// </summary>
11+
internal sealed class ConfigFileAnnotation : IResourceAnnotation
12+
{
13+
public ConfigFileAnnotation(string sourcePath)
14+
{
15+
SourcePath = sourcePath ?? throw new ArgumentNullException(nameof(sourcePath));
16+
}
17+
18+
public string SourcePath { get; }
19+
}

src/Aspire.Hosting.Azure.ServiceBus/AzureServiceBusEmulatorResource.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ namespace Aspire.Hosting.Azure;
1111
/// <param name="innerResource">The inner resource used to store annotations.</param>
1212
public class AzureServiceBusEmulatorResource(AzureServiceBusResource innerResource) : ContainerResource(innerResource.Name), IResource
1313
{
14+
// The path to the emulator configuration files in the container.
15+
internal const string EmulatorConfigFilesPath = "/ServiceBus_Emulator/ConfigFiles";
1416
// The path to the emulator configuration file in the container.
15-
internal const string EmulatorConfigJsonPath = "/ServiceBus_Emulator/ConfigFiles/Config.json";
17+
internal const string EmulatorConfigJsonFile = "Config.json";
1618

1719
private readonly AzureServiceBusResource _innerResource = innerResource ?? throw new ArgumentNullException(nameof(innerResource));
1820

0 commit comments

Comments
 (0)