diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/SavePublishProfileRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/SavePublishProfileRequest.cs
new file mode 100644
index 00000000..132e1163
--- /dev/null
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/SavePublishProfileRequest.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+//
+using System.Collections.Generic;
+using Microsoft.SqlTools.Hosting.Protocol.Contracts;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+
+namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts
+{
+ ///
+ /// Parameters for a DacFx save publish profile request.
+ ///
+ public class SavePublishProfileParams
+ {
+ ///
+ /// Gets or sets the profile path
+ ///
+ public string ProfilePath { get; set; }
+
+ ///
+ /// Gets or sets name for database
+ ///
+ public string? DatabaseName { get; set; }
+
+ ///
+ /// Gets or sets target connection string
+ ///
+ public string? ConnectionString { get; set; }
+
+ ///
+ /// Gets or sets SQLCMD variables for deployment
+ ///
+ public IDictionary? SqlCommandVariableValues { get; set; }
+
+ ///
+ /// Gets or sets the options for deployment
+ ///
+ public DeploymentOptions? DeploymentOptions { get; set; }
+ }
+
+ ///
+ /// Defines the DacFx save publish profile request type
+ ///
+ class SavePublishProfileRequest
+ {
+ public static readonly RequestType Type =
+ RequestType.Create("dacfx/savePublishProfile");
+ }
+}
+
+
+
diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs
index 8c43438d..879539b3 100644
--- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs
+++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs
@@ -15,6 +15,7 @@ using Microsoft.SqlTools.ServiceLayer.Hosting;
using Microsoft.SqlTools.ServiceLayer.TaskServices;
using Microsoft.SqlServer.Dac.Model;
using DacTableDesigner = Microsoft.Data.Tools.Sql.DesignServices.TableDesigner.TableDesigner;
+using Microsoft.SqlTools.ServiceLayer.Utility;
namespace Microsoft.SqlTools.ServiceLayer.DacFx
{
@@ -60,6 +61,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
serviceHost.SetRequestHandler(ParseTSqlScriptRequest.Type, this.HandleParseTSqlScriptRequest, true);
serviceHost.SetRequestHandler(GenerateTSqlModelRequest.Type, this.HandleGenerateTSqlModelRequest, true);
serviceHost.SetRequestHandler(GetObjectsFromTSqlModelRequest.Type, this.HandleGetObjectsFromTSqlModelRequest, true);
+ serviceHost.SetRequestHandler(SavePublishProfileRequest.Type, this.HandleSavePublishProfileRequest, true);
}
///
@@ -311,6 +313,36 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
return;
}
+ ///
+ /// Handles request to save a publish profile
+ ///
+ ///
+ public async Task HandleSavePublishProfileRequest(SavePublishProfileParams parameters, RequestContext requestContext)
+ {
+ await BaseService.RunWithErrorHandling(() =>
+ {
+ if (parameters.ProfilePath != null)
+ {
+ DacProfile profile = new DacProfile();
+ profile.TargetDatabaseName = parameters.DatabaseName;
+ profile.TargetConnectionString = parameters.ConnectionString;
+ //TODO: Set deploy options to pass on to DacFx
+
+ if (parameters.SqlCommandVariableValues != null)
+ {
+ foreach (string key in parameters.SqlCommandVariableValues.Keys)
+ {
+ profile.DeployOptions.SqlCommandVariableValues[key] = parameters.SqlCommandVariableValues[key];
+ }
+ }
+ //TODO: Add return from Save with success/fail status
+ profile.Save(parameters.ProfilePath);
+ }
+ }, requestContext);
+
+ return;
+ }
+
private void ExecuteOperation(DacFxOperation operation, DacFxParams parameters, string taskName, RequestContext requestContext)
{
Task.Run(async () =>
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs
index debff933..1993a651 100644
--- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxServiceTests.cs
@@ -23,6 +23,8 @@ using Microsoft.SqlServer.Dac.Model;
using NUnit.Framework;
using Moq;
using System.Reflection;
+using Microsoft.SqlTools.ServiceLayer.Utility;
+using Microsoft.SqlTools.ServiceLayer.Test.Common.RequestContextMocking;
namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx
{
@@ -868,6 +870,37 @@ Streaming query statement contains a reference to missing output stream 'Missing
}
}
+ //
+ /// Verify that publish profile gets created and saved
+ ///
+ [Test]
+ public async Task ValidateSavePublishProfile()
+ {
+ DacFxService service = new DacFxService();
+ string fileName = "validateSavePublishProfile.publish.xml";
+ string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest");
+ string profileFilePath = Path.Combine(folderPath, fileName);
+ string expectedFile = Path.Combine(publishProfileFolder, fileName);
+
+ var savePublishProfileParams = new SavePublishProfileParams
+ {
+ ProfilePath = profileFilePath,
+ DatabaseName = "testDb",
+ ConnectionString = "testConnString",
+ SqlCommandVariableValues = new Dictionary()
+ {
+ { "testvar", "testval" }
+ }
+ };
+
+ MockRequest requestMock = new();
+
+ await service.HandleSavePublishProfileRequest(savePublishProfileParams, requestMock.Object);
+ requestMock.AssertSuccess(nameof(service.HandleSavePublishProfileRequest));
+
+ await VerifyContentAndCleanupAsync(expectedFile, profileFilePath);
+ }
+
private bool ValidateStreamingJobErrors(ValidateStreamingJobResult expected, ValidateStreamingJobResult actual)
{
return expected.Success == actual.Success
@@ -942,6 +975,24 @@ Streaming query statement contains a reference to missing output stream 'Missing
}
}
+ private async Task VerifyContentAndCleanupAsync(string baselineFilePath, string outputFilePath)
+ {
+ // Verify it was created
+ Assert.True(File.Exists(outputFilePath), "The output file did not get generated.");
+
+ //Verify the contents are same
+ string baseline = await File.ReadAllTextAsync(baselineFilePath);
+ string output = await File.ReadAllTextAsync(outputFilePath);
+
+ Assert.That(output, Is.EqualTo(baseline), $"The output doesn't match the baseline. Expected {Environment.NewLine} {baseline} {Environment.NewLine} Actual {Environment.NewLine} {output}");
+
+ // Remove the file
+ if (File.Exists(outputFilePath))
+ {
+ File.Delete(outputFilePath);
+ }
+ }
+
///
/// Verify expected and actual DeploymentOptions BooleanOptionsDictionary values
///
diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/PublishProfiles/validateSavePublishProfile.publish.xml b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/PublishProfiles/validateSavePublishProfile.publish.xml
new file mode 100644
index 00000000..1d000656
--- /dev/null
+++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/PublishProfiles/validateSavePublishProfile.publish.xml
@@ -0,0 +1,13 @@
+
+
+
+ testDb
+ testConnString
+ 1
+
+
+
+ testval
+
+
+
\ No newline at end of file