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