Skip to content

Commit

Permalink
Add support for Defining DacPac deployment options in SqlDatabaseProj…
Browse files Browse the repository at this point in the history
…ects. (#286)

* Add support for Defining DacPac deployment in SqlDatabaseProjects.

* Undo unrelated changes to deployment logging
  • Loading branch information
nnitkasw authored Nov 29, 2024
1 parent 713c94a commit dd34778
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 8 deletions.
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
{
}
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.Deploy(dacPackage, targetDatabaseName, true, options, cancellationToken);
}
}
}
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);
}
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!>!
16 changes: 16 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md
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

0 comments on commit dd34778

Please sign in to comment.