Skip to content

Commit

Permalink
Add support for local dacpac
Browse files Browse the repository at this point in the history
Signed-off-by: Jonathan Mezach <[email protected]>
  • Loading branch information
jmezach committed Jul 23, 2024
1 parent a81ae23 commit 2b363fd
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 13 deletions.
7 changes: 7 additions & 0 deletions src/MSBuild.Sdk.SqlProj.Aspire/DacpacMetadataAnnotation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Aspire.Hosting.ApplicationModel;

namespace MSBuild.Sdk.SqlProj.Aspire;

public record DacpacMetadataAnnotation(string DacpacPath) : IResourceAnnotation
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,59 @@ namespace Aspire.Hosting;

public static class DataTierApplicationBuilderExtensions
{
/// <summary>
/// Adds a data-tier application resource to the application based on a referenced project.
/// </summary>
/// <typeparam name="TProject">Type that represents the project that produces the data-tier application package.</typeparam>
/// <param name="builder">An <see cref="IDistributedApplicationBuilder"/> instance to add the data-tier application to.</param>
/// <param name="name">Name of the resource.</param>
/// <returns>An <see cref="IResourceBuilder{T}"/> that can be used to further customize the resource.</returns>
public static IResourceBuilder<DataTierApplicationResource> AddDataTierApplication<TProject>(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);

return builder.AddResource(resource)
.WithAnnotation(new TProject());
}

/// <summary>
/// Adds a data-tier application resource to the application based on a data-tier application package file.
/// </summary>
/// <param name="builder">An <see cref="IDistributedApplicationBuilder"/> instance to add the data-tier application to.</param>
/// <param name="name">Name of the resource.</param>
/// <param name="dacpacPath">Path to a data-tier application package file.</param>
/// <returns>An <see cref="IResourceBuilder{T}"/> that can be used to further customize the resource.</returns>
public static IResourceBuilder<DataTierApplicationResource> 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));
}

/// <summary>
/// Publishes the data-tier application to the target <see cref="SqlServerDatabaseResource"/>.
/// </summary>
/// <param name="builder">An <see cref="IResourceBuilder{T}"/> representing the data-tier application to publish.</param>
/// <param name="target">An <see cref="IResourceBuilder{T}"/> representing the target <see cref="SqlServerDatabaseResource"/> to publish the data-tier application to.</param>
/// <returns>An <see cref="IResourceBuilder{T}"/> that can be used to further customize the resource.</returns>
public static IResourceBuilder<DataTierApplicationResource> PublishTo(
this IResourceBuilder<DataTierApplicationResource> builder, IResourceBuilder<SqlServerDatabaseResource> project)
this IResourceBuilder<DataTierApplicationResource> builder, IResourceBuilder<SqlServerDatabaseResource> target)
{
builder.ApplicationBuilder.Services.TryAddLifecycleHook<PublishDataTierApplicationLifecycleHook>();
builder.WithAnnotation(new TargetDatabaseResourceAnnotation(project.Resource.Name), ResourceAnnotationMutationBehavior.Replace);
builder.WithAnnotation(new TargetDatabaseResourceAnnotation(target.Resource.Name), ResourceAnnotationMutationBehavior.Replace);
return builder;
}
}
19 changes: 15 additions & 4 deletions src/MSBuild.Sdk.SqlProj.Aspire/DataTierApplicationResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,20 @@ public sealed class DataTierApplicationResource(string name) : Resource(name)
{
public string GetDacpacPath()
{
var projectMetadata = Annotations.OfType<IProjectMetadata>().Single();
var projectPath = projectMetadata.ProjectPath;
var project = new Project(projectPath);
return project.GetPropertyValue("TargetPath");
var projectMetadata = Annotations.OfType<IProjectMetadata>().FirstOrDefault();
if (projectMetadata != null)
{
var projectPath = projectMetadata.ProjectPath;
var project = new Project(projectPath);
return project.GetPropertyValue("TargetPath");
}

var dacpacMetadata = Annotations.OfType<DacpacMetadataAnnotation>().FirstOrDefault();
if (dacpacMetadata != null)
{
return dacpacMetadata.DacpacPath;
}

throw new InvalidOperationException($"Unable to locate data-tier application package for resource {Name}.");
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.Logging;
Expand All @@ -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));
Expand All @@ -20,12 +22,21 @@ public async Task AfterResourcesCreatedAsync(DistributedApplicationModel applica
{
foreach (var dataTierApplication in application.Resources.OfType<DataTierApplicationResource>())
{
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<TargetDatabaseResourceAnnotation>().Single().TargetDatabaseResourceName;
var targetDatabaseResource = application.Resources.OfType<SqlServerDatabaseResource>().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) });

Expand All @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion test/TestAspireHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
var sql = builder.AddSqlServer("sql")
.AddDatabase("test");

builder.AddDataTierApplication<Projects.TestProject>("db")
builder.AddDataTierApplication<Projects.TestProject>("testproject")
.PublishTo(sql);

builder.AddDataTierApplication("testprojectwithwarnings", "../TestProjectWithWarnings/bin/Debug/netstandard2.0/TestProjectWithWarnings.dacpac")
.PublishTo(sql);

builder.Build().Run();

0 comments on commit 2b363fd

Please sign in to comment.