From 2b363fd332e4046c54f51f55d41f4e515ff0dc74 Mon Sep 17 00:00:00 2001 From: Jonathan Mezach Date: Tue, 23 Jul 2024 15:52:41 +0200 Subject: [PATCH] Add support for local dacpac Signed-off-by: Jonathan Mezach --- .../DacpacMetadataAnnotation.cs | 7 +++ .../DataTierApplicationBuilderExtensions.cs | 44 +++++++++++++++++-- .../DataTierApplicationResource.cs | 19 ++++++-- ...PublishDataTierApplicationLifecycleHook.cs | 19 ++++++-- test/TestAspireHost/Program.cs | 5 ++- 5 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 src/MSBuild.Sdk.SqlProj.Aspire/DacpacMetadataAnnotation.cs diff --git a/src/MSBuild.Sdk.SqlProj.Aspire/DacpacMetadataAnnotation.cs b/src/MSBuild.Sdk.SqlProj.Aspire/DacpacMetadataAnnotation.cs new file mode 100644 index 00000000..214d04fe --- /dev/null +++ b/src/MSBuild.Sdk.SqlProj.Aspire/DacpacMetadataAnnotation.cs @@ -0,0 +1,7 @@ +using Aspire.Hosting.ApplicationModel; + +namespace MSBuild.Sdk.SqlProj.Aspire; + +public record DacpacMetadataAnnotation(string DacpacPath) : IResourceAnnotation +{ +} diff --git a/src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationBuilderExtensions.cs b/src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationBuilderExtensions.cs index ca01155a..7ffb37d6 100644 --- a/src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationBuilderExtensions.cs +++ b/src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationBuilderExtensions.cs @@ -7,11 +7,21 @@ namespace Aspire.Hosting; public static class DataTierApplicationBuilderExtensions { + /// + /// Adds a data-tier application resource to the application based on a referenced project. + /// + /// Type that represents the project that produces the data-tier application package. + /// An instance to add the data-tier application to. + /// Name of the resource. + /// An that can be used to further customize the resource. public static IResourceBuilder AddDataTierApplication(this IDistributedApplicationBuilder builder, string name) where TProject : IProjectMetadata, new() { - MSBuildLocator.RegisterInstance(MSBuildLocator.QueryVisualStudioInstances().OrderByDescending( - instance => instance.Version).First()); + if (!MSBuildLocator.IsRegistered) + { + MSBuildLocator.RegisterInstance(MSBuildLocator.QueryVisualStudioInstances().OrderByDescending( + instance => instance.Version).First()); + } var resource = new DataTierApplicationResource(name); @@ -19,11 +29,37 @@ public static IResourceBuilder AddDataTierApplicati .WithAnnotation(new TProject()); } + /// + /// Adds a data-tier application resource to the application based on a data-tier application package file. + /// + /// An instance to add the data-tier application to. + /// Name of the resource. + /// Path to a data-tier application package file. + /// An that can be used to further customize the resource. + public static IResourceBuilder AddDataTierApplication(this IDistributedApplicationBuilder builder, string name, string dacpacPath) + { + var resource = new DataTierApplicationResource(name); + + if (!Path.IsPathRooted(dacpacPath)) + { + dacpacPath = Path.Combine(builder.AppHostDirectory, dacpacPath); + } + + return builder.AddResource(resource) + .WithAnnotation(new DacpacMetadataAnnotation(dacpacPath)); + } + + /// + /// Publishes the data-tier application to the target . + /// + /// An representing the data-tier application to publish. + /// An representing the target to publish the data-tier application to. + /// An that can be used to further customize the resource. public static IResourceBuilder PublishTo( - this IResourceBuilder builder, IResourceBuilder project) + this IResourceBuilder builder, IResourceBuilder target) { builder.ApplicationBuilder.Services.TryAddLifecycleHook(); - builder.WithAnnotation(new TargetDatabaseResourceAnnotation(project.Resource.Name), ResourceAnnotationMutationBehavior.Replace); + builder.WithAnnotation(new TargetDatabaseResourceAnnotation(target.Resource.Name), ResourceAnnotationMutationBehavior.Replace); return builder; } } diff --git a/src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationResource.cs b/src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationResource.cs index a8f9fef7..51685563 100644 --- a/src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationResource.cs +++ b/src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationResource.cs @@ -8,9 +8,20 @@ public sealed class DataTierApplicationResource(string name) : Resource(name) { public string GetDacpacPath() { - var projectMetadata = Annotations.OfType().Single(); - var projectPath = projectMetadata.ProjectPath; - var project = new Project(projectPath); - return project.GetPropertyValue("TargetPath"); + var projectMetadata = Annotations.OfType().FirstOrDefault(); + if (projectMetadata != null) + { + var projectPath = projectMetadata.ProjectPath; + var project = new Project(projectPath); + return project.GetPropertyValue("TargetPath"); + } + + var dacpacMetadata = Annotations.OfType().FirstOrDefault(); + if (dacpacMetadata != null) + { + return dacpacMetadata.DacpacPath; + } + + throw new InvalidOperationException($"Unable to locate data-tier application package for resource {Name}."); } } diff --git a/src/MSBuild.Sdk.SqlProj.Aspire/PublishDataTierApplicationLifecycleHook.cs b/src/MSBuild.Sdk.SqlProj.Aspire/PublishDataTierApplicationLifecycleHook.cs index 44a57f71..e3ecf21f 100644 --- a/src/MSBuild.Sdk.SqlProj.Aspire/PublishDataTierApplicationLifecycleHook.cs +++ b/src/MSBuild.Sdk.SqlProj.Aspire/PublishDataTierApplicationLifecycleHook.cs @@ -1,3 +1,4 @@ +using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Lifecycle; using Microsoft.Extensions.Logging; @@ -10,7 +11,8 @@ public class PublishDataTierApplicationLifecycleHook : IDistributedApplicationLi private readonly ResourceLoggerService _resourceLoggerService; private readonly ResourceNotificationService _resourceNotificationService; - public PublishDataTierApplicationLifecycleHook(ResourceLoggerService resourceLoggerService, ResourceNotificationService resourceNotificationService) + public PublishDataTierApplicationLifecycleHook(ResourceLoggerService resourceLoggerService, + ResourceNotificationService resourceNotificationService, DistributedApplicationOptions options) { _resourceLoggerService = resourceLoggerService ?? throw new ArgumentNullException(nameof(resourceLoggerService)); _resourceNotificationService = resourceNotificationService ?? throw new ArgumentNullException(nameof(resourceNotificationService)); @@ -20,12 +22,21 @@ public async Task AfterResourcesCreatedAsync(DistributedApplicationModel applica { foreach (var dataTierApplication in application.Resources.OfType()) { + var logger = _resourceLoggerService.GetLogger(dataTierApplication); + + var dacpacPath = dataTierApplication.GetDacpacPath(); + if (!File.Exists(dacpacPath)) + { + logger.LogError("Data-tier application package not found at path {DacpacPath}.", dacpacPath); + await _resourceNotificationService.PublishUpdateAsync(dataTierApplication, + state => state with { State = new ResourceStateSnapshot("Failed", KnownResourceStateStyles.Error) }); + continue; + } + var targetDatabaseResourceName = dataTierApplication.Annotations.OfType().Single().TargetDatabaseResourceName; var targetDatabaseResource = application.Resources.OfType().Single(r => r.Name == targetDatabaseResourceName); var connectionString = await targetDatabaseResource.ConnectionStringExpression.GetValueAsync(cancellationToken); - var logger = _resourceLoggerService.GetLogger(dataTierApplication); - await _resourceNotificationService.PublishUpdateAsync(dataTierApplication, state => state with { State = new ResourceStateSnapshot("Publishing", KnownResourceStateStyles.Info) }); @@ -34,7 +45,7 @@ await _resourceNotificationService.PublishUpdateAsync(dataTierApplication, var dacServices = new DacServices(connectionString); dacServices.Message += (sender, args) => logger.LogInformation(args.Message.ToString()); - var dacpacPackage = DacPackage.Load(dataTierApplication.GetDacpacPath(), DacSchemaModelStorageType.Memory); + var dacpacPackage = DacPackage.Load(dacpacPath, DacSchemaModelStorageType.Memory); dacServices.Deploy(dacpacPackage, targetDatabaseResource.Name, true, new DacDeployOptions(), cancellationToken); await _resourceNotificationService.PublishUpdateAsync(dataTierApplication, diff --git a/test/TestAspireHost/Program.cs b/test/TestAspireHost/Program.cs index 6ded02e7..bbdf166b 100644 --- a/test/TestAspireHost/Program.cs +++ b/test/TestAspireHost/Program.cs @@ -3,7 +3,10 @@ var sql = builder.AddSqlServer("sql") .AddDatabase("test"); -builder.AddDataTierApplication("db") +builder.AddDataTierApplication("testproject") + .PublishTo(sql); + +builder.AddDataTierApplication("testprojectwithwarnings", "../TestProjectWithWarnings/bin/Debug/netstandard2.0/TestProjectWithWarnings.dacpac") .PublishTo(sql); builder.Build().Run();