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
Changes from all commits
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
{
}
Original file line number Diff line number Diff line change
@@ -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;

@@ -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!>!
Original file line number Diff line number Diff line change
@@ -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
@@ -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<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>
Original file line number Diff line number Diff line change
@@ -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) });
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;

@@ -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;

@@ -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()
{