Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Defining DacPac deployment options in SqlDatabaseProjects. #286

Merged
merged 2 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.SqlServer.Dac;

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Represents a metadata annotation that specifies dacpac deployment options.
/// </summary>
/// <param name="ConfigureDeploymentOptions">deployment options</param>
public record ConfigureDacDeployOptionsAnnotation(Action<DacDeployOptions> ConfigureDeploymentOptions) : IResourceAnnotation
jmezach marked this conversation as resolved.
Show resolved Hide resolved
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects;
/// </summary>
internal class DacpacDeployer : IDacpacDeployer
{
/// <inheritdoc cref="IDacpacDeployer.Deploy(string, string, string, ILogger, CancellationToken)" />
public void Deploy(string dacpacPath, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken)
/// <inheritdoc cref="IDacpacDeployer.Deploy(string, DacDeployOptions, string, string, ILogger, 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());
jmezach marked this conversation as resolved.
Show resolved Hide resolved
dacServices.Deploy(dacPackage, targetDatabaseName, true, options, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.Extensions.Logging;

namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects;

/// <summary>
/// Provides source generated logging methods for the <see cref="DacpacDeployer"/> class.
/// </summary>
internal static partial class DacpacDeploymentLogger
{
[LoggerMessage(0, LogLevel.Information, "{message}")]
public static partial void LogDeploymentMessage(this ILogger logger, String message);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using Microsoft.SqlServer.Dac;

namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects;

Expand All @@ -12,9 +13,10 @@ internal interface IDacpacDeployer
/// using the provided <paramref name="targetDatabaseName">database name</paramref>.
/// </summary>
/// <param name="dacpacPath">Path to the .dacpac file to deploy.</param>
/// <param name="options">Instance of <see cref="T:DacDeployOptions" /> that specifies properties that affect various aspects of the deployment.</param>
/// <param name="targetConnectionString">Connection string to the SQL Server.</param>
/// <param name="targetDatabaseName">Name of the target database to deploy to.</param>
/// <param name="deploymentLogger">An <see cref="ILogger" /> to write the deployment log to.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the deployment operation.</param>
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronpowell Just out of curiosity, but did the PublicAPI.Shipped.txt file get automagically updated when we released 9.0? I don't remember doing this, but it must have happened somewhere so I'm just curious how that works exactly ;)

Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
#nullable enable

Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation
Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDacDeployOptionsAnnotation(System.Action<Microsoft.SqlServer.Dac.DacDeployOptions!>! ConfigureDeploymentOptions) -> void
Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDeploymentOptions.get -> System.Action<Microsoft.SqlServer.Dac.DacDeployOptions!>!
Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDeploymentOptions.init -> void
static Aspire.Hosting.SqlProjectBuilderExtensions.WithConfigureDacDeployOptions(this Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SqlProjectResource!>! builder, System.Action<Microsoft.SqlServer.Dac.DacDeployOptions!>! configureDeploymentOptions) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SqlProjectResource!>!
Original file line number Diff line number Diff line change
Expand Up @@ -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();
```
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -72,6 +73,21 @@ public static IResourceBuilder<SqlProjectResource> WithDacpac(this IResourceBuil
return builder.WithAnnotation(new DacpacMetadataAnnotation(dacpacPath));
}

/// <summary>
/// Adds a delegate annotation for configuring dacpac deployment options to the <see cref="SqlProjectResource"/>.
/// </summary>
/// <param name="builder">An <see cref="IResourceBuilder{T}"/> representing the SQL Server Database project.</param>
/// <param name="configureDeploymentOptions">The delegate for configuring dacpac deployment options</param>
/// <returns>An <see cref="IResourceBuilder{T}"/> that can be used to further customize the resource.</returns>
public static IResourceBuilder<SqlProjectResource> WithConfigureDacDeployOptions(this IResourceBuilder<SqlProjectResource> builder, Action<DacDeployOptions> configureDeploymentOptions)
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));
ArgumentNullException.ThrowIfNull(configureDeploymentOptions);

return builder
.WithAnnotation(new ConfigureDacDeployOptionsAnnotation(configureDeploymentOptions));
}

/// <summary>
/// Publishes the SQL Server Database project to the target <see cref="SqlServerDatabaseResource"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ await resourceNotificationService.PublishUpdateAsync(sqlProject,
return;
}

var options = sqlProject.GetDacpacDeployOptions();

var connectionString = await target.ConnectionStringExpression.GetValueAsync(cancellationToken);
if (connectionString is null)
{
Expand All @@ -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) });
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Build.Evaluation;
using Microsoft.SqlServer.Dac;

namespace Aspire.Hosting.ApplicationModel;

Expand Down Expand Up @@ -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<ConfigureDacDeployOptionsAnnotation>(out var configureAnnotation))
{
configureAnnotation.ConfigureDeploymentOptions(options);
}

return options;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Aspire.Hosting;
using Microsoft.SqlServer.Dac;

namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests;

Expand Down Expand Up @@ -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<DistributedApplicationModel>();

// Assert
var sqlProjectResource = Assert.Single(appModel.Resources.OfType<SqlProjectResource>());
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<DacDeployOptions> configureAction = options => options.IncludeCompositeObjects = true;

appBuilder.AddSqlProject("MySqlProject").WithConfigureDacDeployOptions(configureAction);

// Act
using var app = appBuilder.Build();
var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

// Assert
var sqlProjectResource = Assert.Single(appModel.Resources.OfType<SqlProjectResource>());
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()
{
Expand Down
Loading