From 104393f575801724af27725fe3a98c3183c0b74f Mon Sep 17 00:00:00 2001 From: Kasper Wegmann Date: Mon, 25 Nov 2024 12:36:12 +0100 Subject: [PATCH 1/2] Add support for Defining DacPac deployment in SqlDatabaseProjects. --- .../ConfigureDacDeployOptionsAnnotation.cs | 11 +++++ .../DacpacDeployer.cs | 12 ++--- .../DacpacDeploymentLogger.cs | 12 +++++ .../IDacpacDeployer.cs | 4 +- .../PublicAPI.Unshipped.txt | 6 ++- .../README.md | 16 +++++++ .../SqlProjectBuilderExtensions.cs | 16 +++++++ .../SqlProjectPublishService.cs | 4 +- .../SqlProjectResource.cs | 13 +++++ .../AddSqlProjectTests.cs | 48 +++++++++++++++++++ 10 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ConfigureDacDeployOptionsAnnotation.cs create mode 100644 src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeploymentLogger.cs diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ConfigureDacDeployOptionsAnnotation.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ConfigureDacDeployOptionsAnnotation.cs new file mode 100644 index 00000000..70ec72c9 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ConfigureDacDeployOptionsAnnotation.cs @@ -0,0 +1,11 @@ +using Microsoft.SqlServer.Dac; + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// Represents a metadata annotation that specifies dacpac deployment options. +/// +/// deployment options +public record ConfigureDacDeployOptionsAnnotation(Action ConfigureDeploymentOptions) : IResourceAnnotation +{ +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs index 681bcfa5..5a4b707a 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs @@ -8,12 +8,12 @@ namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects; /// internal class DacpacDeployer : IDacpacDeployer { - /// - public void Deploy(string dacpacPath, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken) + /// + public void Deploy(string dacpacPath, DacDeployOptions options, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken) { - var dacPackage = DacPackage.Load(dacpacPath, DacSchemaModelStorageType.Memory); + using var dacPackage = DacPackage.Load(dacpacPath, DacSchemaModelStorageType.Memory); var dacServices = new DacServices(targetConnectionString); - dacServices.Message += (sender, args) => deploymentLogger.LogInformation(args.Message.ToString()); - dacServices.Deploy(dacPackage, targetDatabaseName, true, new DacDeployOptions(), cancellationToken); + dacServices.Message += (_, args) => deploymentLogger.LogDeploymentMessage(args.Message.ToString()); + dacServices.Deploy(dacPackage, targetDatabaseName, true, options, cancellationToken); } -} +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeploymentLogger.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeploymentLogger.cs new file mode 100644 index 00000000..43cf896a --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeploymentLogger.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.Logging; + +namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects; + +/// +/// Provides source generated logging methods for the class. +/// +internal static partial class DacpacDeploymentLogger +{ + [LoggerMessage(0, LogLevel.Information, "{message}")] + public static partial void LogDeploymentMessage(this ILogger logger, String message); +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/IDacpacDeployer.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/IDacpacDeployer.cs index 4e120737..a9d9b924 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/IDacpacDeployer.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/IDacpacDeployer.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.SqlServer.Dac; namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects; @@ -12,9 +13,10 @@ internal interface IDacpacDeployer /// using the provided database name. /// /// Path to the .dacpac file to deploy. + /// Instance of that specifies properties that affect various aspects of the deployment. /// Connection string to the SQL Server. /// Name of the target database to deploy to. /// An to write the deployment log to. /// A that can be used to cancel the deployment operation. - void Deploy(string dacpacPath, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken); + void Deploy(string dacpacPath, DacDeployOptions options, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken); } diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/PublicAPI.Unshipped.txt index 074c6ad1..d5d6b090 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/PublicAPI.Unshipped.txt +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/PublicAPI.Unshipped.txt @@ -1,2 +1,6 @@ #nullable enable - +Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation +Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDacDeployOptionsAnnotation(System.Action! ConfigureDeploymentOptions) -> void +Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDeploymentOptions.get -> System.Action! +Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDeploymentOptions.init -> void +static Aspire.Hosting.SqlProjectBuilderExtensions.WithConfigureDacDeployOptions(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.Action! configureDeploymentOptions) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md index 3e083cbf..21df7927 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md @@ -45,5 +45,21 @@ builder.AddSqlProject("mysqlproj") .WithDacpac("path/to/mysqlproj.dacpac") .WithReference(sql); +builder.Build().Run(); +``` + +## Deployment options support +Define options that affect the behavior of package deployment. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var sql = builder.AddSqlServer("sql") + .AddDatabase("test"); + +builder.AddSqlProject("mysqlproj") + .WithConfigureDacDeployOptions(options => options.IncludeCompositeObjects = true) + .WithReference(sql); + builder.Build().Run(); ``` \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs index 475cd2d9..4ea2b74d 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects; +using Microsoft.SqlServer.Dac; namespace Aspire.Hosting; @@ -72,6 +73,21 @@ public static IResourceBuilder WithDacpac(this IResourceBuil return builder.WithAnnotation(new DacpacMetadataAnnotation(dacpacPath)); } + /// + /// Adds a delegate annotation for configuring dacpac deployment options to the . + /// + /// An representing the SQL Server Database project. + /// The delegate for configuring dacpac deployment options + /// An that can be used to further customize the resource. + public static IResourceBuilder WithConfigureDacDeployOptions(this IResourceBuilder builder, Action configureDeploymentOptions) + { + ArgumentNullException.ThrowIfNull(builder, nameof(builder)); + ArgumentNullException.ThrowIfNull(configureDeploymentOptions); + + return builder + .WithAnnotation(new ConfigureDacDeployOptionsAnnotation(configureDeploymentOptions)); + } + /// /// Publishes the SQL Server Database project to the target . /// diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectPublishService.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectPublishService.cs index 98716a32..3d37d0c8 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectPublishService.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectPublishService.cs @@ -21,6 +21,8 @@ await resourceNotificationService.PublishUpdateAsync(sqlProject, return; } + var options = sqlProject.GetDacpacDeployOptions(); + var connectionString = await target.ConnectionStringExpression.GetValueAsync(cancellationToken); if (connectionString is null) { @@ -33,7 +35,7 @@ await resourceNotificationService.PublishUpdateAsync(sqlProject, await resourceNotificationService.PublishUpdateAsync(sqlProject, state => state with { State = new ResourceStateSnapshot("Publishing", KnownResourceStateStyles.Info) }); - deployer.Deploy(dacpacPath, connectionString, target.DatabaseName, logger, cancellationToken); + deployer.Deploy(dacpacPath, options, connectionString, target.DatabaseName, logger, cancellationToken); await resourceNotificationService.PublishUpdateAsync(sqlProject, state => state with { State = new ResourceStateSnapshot(KnownResourceStates.Finished, KnownResourceStateStyles.Success) }); diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs index 004f84cd..68391fde 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs @@ -1,4 +1,5 @@ using Microsoft.Build.Evaluation; +using Microsoft.SqlServer.Dac; namespace Aspire.Hosting.ApplicationModel; @@ -33,4 +34,16 @@ internal string GetDacpacPath() throw new InvalidOperationException($"Unable to locate SQL Server Database project package for resource {Name}."); } + + internal DacDeployOptions GetDacpacDeployOptions() + { + var options = new DacDeployOptions(); + + if (this.TryGetLastAnnotation(out var configureAnnotation)) + { + configureAnnotation.ConfigureDeploymentOptions(options); + } + + return options; + } } diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs index 559e3214..df7d0f56 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs @@ -1,4 +1,5 @@ using Aspire.Hosting; +using Microsoft.SqlServer.Dac; namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests; @@ -47,6 +48,53 @@ public void AddSqlProject_WithExplicitPath() Assert.True(File.Exists(dacpacPath)); } + [Fact] + public void AddSqlProject_WithoutDeploymentOptions() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + + appBuilder.AddSqlProject("MySqlProject"); + + // Act + using var app = appBuilder.Build(); + var appModel = app.Services.GetRequiredService(); + + // Assert + var sqlProjectResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("MySqlProject", sqlProjectResource.Name); + + Assert.False(sqlProjectResource.TryGetLastAnnotation(out ConfigureDacDeployOptionsAnnotation? _)); + + var options = sqlProjectResource.GetDacpacDeployOptions(); + Assert.NotNull(options); + Assert.Equivalent(new DacDeployOptions(), options); + } + + [Fact] + public void AddSqlProject_WithDeploymentOptions() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + Action configureAction = options => options.IncludeCompositeObjects = true; + + appBuilder.AddSqlProject("MySqlProject").WithConfigureDacDeployOptions(configureAction); + + // Act + using var app = appBuilder.Build(); + var appModel = app.Services.GetRequiredService(); + + // Assert + var sqlProjectResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("MySqlProject", sqlProjectResource.Name); + + Assert.True(sqlProjectResource.TryGetLastAnnotation(out ConfigureDacDeployOptionsAnnotation? configureDacDeployOptionsAnnotation)); + Assert.Same(configureAction, configureDacDeployOptionsAnnotation.ConfigureDeploymentOptions); + + var options = sqlProjectResource.GetDacpacDeployOptions(); + Assert.True(options.IncludeCompositeObjects); + } + [Fact] public void PublishTo_AddsRequiredServices() { From 7d0496b0115617a6f86d42fde56ab0f9b9b766f5 Mon Sep 17 00:00:00 2001 From: Kasper Wegmann Date: Tue, 26 Nov 2024 18:33:31 +0100 Subject: [PATCH 2/2] Undo unrelated changes to deployment logging --- .../DacpacDeployer.cs | 2 +- .../DacpacDeploymentLogger.cs | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeploymentLogger.cs diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs index 5a4b707a..9afb22e7 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs @@ -13,7 +13,7 @@ public void Deploy(string dacpacPath, DacDeployOptions options, string targetCon { using var dacPackage = DacPackage.Load(dacpacPath, DacSchemaModelStorageType.Memory); var dacServices = new DacServices(targetConnectionString); - dacServices.Message += (_, args) => deploymentLogger.LogDeploymentMessage(args.Message.ToString()); + dacServices.Message += (sender, args) => deploymentLogger.LogInformation(args.Message.ToString()); dacServices.Deploy(dacPackage, targetDatabaseName, true, options, cancellationToken); } } \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeploymentLogger.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeploymentLogger.cs deleted file mode 100644 index 43cf896a..00000000 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeploymentLogger.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects; - -/// -/// Provides source generated logging methods for the class. -/// -internal static partial class DacpacDeploymentLogger -{ - [LoggerMessage(0, LogLevel.Information, "{message}")] - public static partial void LogDeploymentMessage(this ILogger logger, String message); -} \ No newline at end of file