From 1917100bfb56f649da43a5953c0bd1ef40e40289 Mon Sep 17 00:00:00 2001 From: kisantia <31145923+kisantia@users.noreply.github.com> Date: Fri, 25 Jan 2019 11:34:28 -0800 Subject: [PATCH] Add DacFx Generate Deploy Script operation (#759) * Adding DacFx Generate Deploy Script operation --- .../Contracts/GenerateDeployScriptRequest.cs | 40 ++++++++++ .../DacFx/DacFxOperation.cs | 11 +-- .../DacFx/DacFxService.cs | 51 ++++++++++-- .../DacFx/DeployOperation.cs | 3 +- .../DacFx/ExportOperation.cs | 3 +- .../DacFx/ExtractOperation.cs | 3 +- .../DacFx/GenerateDeployScriptOperation.cs | 48 +++++++++++ .../DacFx/ImportOperation.cs | 3 +- .../DacFx/DacFxserviceTests.cs | 79 ++++++++++++++++--- 9 files changed, 211 insertions(+), 30 deletions(-) create mode 100644 src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateDeployScriptRequest.cs create mode 100644 src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateDeployScriptOperation.cs diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateDeployScriptRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateDeployScriptRequest.cs new file mode 100644 index 00000000..d05ef8df --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/Contracts/GenerateDeployScriptRequest.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Microsoft.SqlTools.ServiceLayer.TaskServices; +using Microsoft.SqlTools.ServiceLayer.Utility; + +namespace Microsoft.SqlTools.ServiceLayer.DacFx.Contracts +{ + /// + /// Parameters for a DacFx generate deploy script request. + /// + public class GenerateDeployScriptParams : DacFxParams + { + /// + /// Gets or sets the filepath where to save the generated script + /// + public string ScriptFilePath { get; set; } + + /// + /// Gets or sets whether a Deployment Report should be generated during deploy. + /// + public bool GenerateDeploymentReport { get; set; } + + /// + /// Gets or sets the filepath where to save the deployment report + /// + public string DeploymentReportFilePath { get; set; } + } + + /// + /// Defines the DacFx generate deploy script request type + /// + class GenerateDeployScriptRequest + { + public static readonly RequestType Type = + RequestType.Create("dacfx/generateDeploymentScript"); + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxOperation.cs index 151760f1..e8037aff 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxOperation.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using Microsoft.SqlServer.Dac; +using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.Utility; using System; @@ -27,14 +28,14 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx public SqlTask SqlTask { get; set; } - protected SqlConnection SqlConnection { get; private set; } + protected string ConnectionString { get; private set; } protected DacServices DacServices { get; private set; } - protected DacFxOperation(SqlConnection sqlConnection) + protected DacFxOperation(ConnectionInfo connInfo) { - Validate.IsNotNull("sqlConnection", sqlConnection); - this.SqlConnection = sqlConnection; + Validate.IsNotNull("connectionInfo", connInfo); + this.ConnectionString = ConnectionService.BuildConnectionString(connInfo.ConnectionDetails); this.OperationId = Guid.NewGuid().ToString(); } @@ -78,7 +79,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx try { - this.DacServices = new DacServices(this.SqlConnection.ConnectionString); + this.DacServices = new DacServices(this.ConnectionString); Execute(); } catch (Exception e) diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs index 5e7609b8..7ae9b2d4 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DacFxService.cs @@ -43,6 +43,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx serviceHost.SetRequestHandler(ImportRequest.Type, this.HandleImportRequest); serviceHost.SetRequestHandler(ExtractRequest.Type, this.HandleExtractRequest); serviceHost.SetRequestHandler(DeployRequest.Type, this.HandleDeployRequest); + serviceHost.SetRequestHandler(GenerateDeployScriptRequest.Type, this.HandleGenerateDeployScriptRequest); } /// @@ -64,8 +65,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx out connInfo); if (connInfo != null) { - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "Export"); - ExportOperation operation = new ExportOperation(parameters, sqlConn); + ExportOperation operation = new ExportOperation(parameters, connInfo); await ExecuteOperation(operation, parameters, "Export bacpac", requestContext); } } @@ -89,8 +89,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx out connInfo); if (connInfo != null) { - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "Import"); - ImportOperation operation = new ImportOperation(parameters, sqlConn); + ImportOperation operation = new ImportOperation(parameters, connInfo); await ExecuteOperation(operation, parameters, "Import bacpac", requestContext); } } @@ -114,8 +113,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx out connInfo); if (connInfo != null) { - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "Extract"); - ExtractOperation operation = new ExtractOperation(parameters, sqlConn); + ExtractOperation operation = new ExtractOperation(parameters, connInfo); await ExecuteOperation(operation, parameters, "Extract dacpac", requestContext); } } @@ -139,8 +137,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx out connInfo); if (connInfo != null) { - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(connInfo, "Deploy"); - DeployOperation operation = new DeployOperation(parameters, sqlConn); + DeployOperation operation = new DeployOperation(parameters, connInfo); await ExecuteOperation(operation, parameters, "Deploy dacpac", requestContext); } } @@ -150,6 +147,44 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx } } + /// + /// Handles request to generate deploy script + /// + /// + public async Task HandleGenerateDeployScriptRequest(GenerateDeployScriptParams parameters, RequestContext requestContext) + { + try + { + ConnectionInfo connInfo; + ConnectionServiceInstance.TryFindConnection( + parameters.OwnerUri, + out connInfo); + if (connInfo != null) + { + GenerateDeployScriptOperation operation = new GenerateDeployScriptOperation(parameters, connInfo); + SqlTask sqlTask = null; + TaskMetadata metadata = TaskMetadata.Create(parameters, "Generate script", operation, ConnectionServiceInstance); + + // want to show filepath in task history instead of server and database + metadata.ServerName = parameters.ScriptFilePath; + metadata.DatabaseName = ""; + + sqlTask = SqlTaskManagerInstance.CreateAndRun(metadata); + + await requestContext.SendResult(new DacFxResult() + { + OperationId = operation.OperationId, + Success = true, + ErrorMessage = "" + }); + } + } + catch (Exception e) + { + await requestContext.SendError(e); + } + } + private async Task ExecuteOperation(DacFxOperation operation, DacFxParams parameters, string taskName, RequestContext requestContext) { SqlTask sqlTask = null; diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs index b636adcc..f6d07bba 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/DeployOperation.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using Microsoft.SqlServer.Dac; +using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.Utility; @@ -19,7 +20,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx { public DeployParams Parameters { get; } - public DeployOperation(DeployParams parameters, SqlConnection sqlConnection) : base(sqlConnection) + public DeployOperation(DeployParams parameters, ConnectionInfo connInfo) : base(connInfo) { Validate.IsNotNull("parameters", parameters); this.Parameters = parameters; diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExportOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExportOperation.cs index 6cda9068..15cb1bf1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExportOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExportOperation.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using Microsoft.SqlServer.Dac; +using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.Utility; @@ -19,7 +20,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx { public ExportParams Parameters { get; } - public ExportOperation(ExportParams parameters, SqlConnection sqlConnection): base(sqlConnection) + public ExportOperation(ExportParams parameters, ConnectionInfo connInfo) : base(connInfo) { Validate.IsNotNull("parameters", parameters); this.Parameters = parameters; diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExtractOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExtractOperation.cs index 09b6b130..4e4b6180 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExtractOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ExtractOperation.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using Microsoft.SqlServer.Dac; +using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.Utility; @@ -19,7 +20,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx { public ExtractParams Parameters { get; } - public ExtractOperation(ExtractParams parameters, SqlConnection sqlConnection): base(sqlConnection) + public ExtractOperation(ExtractParams parameters, ConnectionInfo connInfo) : base(connInfo) { Validate.IsNotNull("parameters", parameters); this.Parameters = parameters; diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateDeployScriptOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateDeployScriptOperation.cs new file mode 100644 index 00000000..0ef7bee1 --- /dev/null +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/GenerateDeployScriptOperation.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +using Microsoft.SqlServer.Dac; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; +using Microsoft.SqlTools.ServiceLayer.TaskServices; +using Microsoft.SqlTools.Utility; +using System; +using System.Data.SqlClient; +using System.Diagnostics; +using System.IO; + +namespace Microsoft.SqlTools.ServiceLayer.DacFx +{ + /// + /// Class to represent an in-progress generate deploy script operation + /// + class GenerateDeployScriptOperation : DacFxOperation + { + public GenerateDeployScriptParams Parameters { get; } + + public GenerateDeployScriptOperation(GenerateDeployScriptParams parameters, ConnectionInfo connInfo) : base(connInfo) + { + Validate.IsNotNull("parameters", parameters); + this.Parameters = parameters; + } + + public override void Execute() + { + DacPackage dacpac = DacPackage.Load(this.Parameters.PackageFilePath); + PublishOptions publishOptions = new PublishOptions(); + publishOptions.GenerateDeploymentReport = this.Parameters.GenerateDeploymentReport; + publishOptions.CancelToken = this.CancellationToken; + publishOptions.DatabaseScriptPath = this.Parameters.ScriptFilePath; + // master script is only used if the target is Azure SQL db and the script contains all operations that must be done against the master database + publishOptions.MasterDbScriptPath = Path.Combine(Path.GetDirectoryName(this.Parameters.ScriptFilePath), string.Concat("master_", Path.GetFileName(this.Parameters.ScriptFilePath))); + + PublishResult result = this.DacServices.Script(dacpac, this.Parameters.DatabaseName, publishOptions); + + if(this.Parameters.GenerateDeploymentReport && !string.IsNullOrEmpty(this.Parameters.DeploymentReportFilePath)) + { + File.WriteAllText(this.Parameters.DeploymentReportFilePath, result.DeploymentReport); + } + } + } +} diff --git a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ImportOperation.cs b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ImportOperation.cs index fbc51347..df277f36 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/DacFx/ImportOperation.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/DacFx/ImportOperation.cs @@ -3,6 +3,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using Microsoft.SqlServer.Dac; +using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.DacFx.Contracts; using Microsoft.SqlTools.ServiceLayer.TaskServices; using Microsoft.SqlTools.Utility; @@ -19,7 +20,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx { public ImportParams Parameters { get; } - public ImportOperation(ImportParams parameters, SqlConnection sqlConnection): base(sqlConnection) + public ImportOperation(ImportParams parameters, ConnectionInfo connInfo) : base(connInfo) { Validate.IsNotNull("parameters", parameters); this.Parameters = parameters; diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs index 612bda07..d97ce325 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/DacFx/DacFxserviceTests.cs @@ -42,9 +42,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", testdb.DatabaseName)) }; - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Export"); DacFxService service = new DacFxService(); - ExportOperation operation = new ExportOperation(exportParams, sqlConn); + ExportOperation operation = new ExportOperation(exportParams, result.ConnectionInfo); service.PerformOperation(operation); // cleanup @@ -71,9 +70,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", sourceDb.DatabaseName)) }; - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Import"); DacFxService service = new DacFxService(); - ExportOperation exportOperation = new ExportOperation(exportParams, sqlConn); + ExportOperation exportOperation = new ExportOperation(exportParams, result.ConnectionInfo); service.PerformOperation(exportOperation); // import the created bacpac @@ -86,7 +84,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx DatabaseName = string.Concat(sourceDb.DatabaseName, "-imported") }; - ImportOperation importOperation = new ImportOperation(importParams, sqlConn); + ImportOperation importOperation = new ImportOperation(importParams, result.ConnectionInfo); service.PerformOperation(importOperation); SqlTestDb targetDb = SqlTestDb.CreateFromExisting(importParams.DatabaseName); @@ -116,9 +114,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx ApplicationVersion = new Version(1, 0) }; - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Extract"); DacFxService service = new DacFxService(); - ExtractOperation operation = new ExtractOperation(extractParams, sqlConn); + ExtractOperation operation = new ExtractOperation(extractParams, result.ConnectionInfo); service.PerformOperation(operation); // cleanup @@ -147,9 +144,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx ApplicationVersion = new Version(1, 0) }; - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Deploy"); DacFxService service = new DacFxService(); - ExtractOperation extractOperation = new ExtractOperation(extractParams, sqlConn); + ExtractOperation extractOperation = new ExtractOperation(extractParams, result.ConnectionInfo); service.PerformOperation(extractOperation); // deploy the created dacpac @@ -164,8 +160,9 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx UpgradeExisting = false }; - DeployOperation deployOperation = new DeployOperation(deployParams, sqlConn); - service.PerformOperation(deployOperation); SqlTestDb targetDb = SqlTestDb.CreateFromExisting(deployParams.DatabaseName); + DeployOperation deployOperation = new DeployOperation(deployParams, result.ConnectionInfo); + service.PerformOperation(deployOperation); + SqlTestDb targetDb = SqlTestDb.CreateFromExisting(deployParams.DatabaseName); // cleanup VerifyAndCleanup(extractParams.PackageFilePath); @@ -191,9 +188,8 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx PackageFilePath = Path.Combine(folderPath, string.Format("{0}.bacpac", testdb.DatabaseName)) }; - SqlConnection sqlConn = ConnectionService.OpenSqlConnection(result.ConnectionInfo, "Export"); DacFxService service = new DacFxService(); - ExportOperation operation = new ExportOperation(exportParams, sqlConn); + ExportOperation operation = new ExportOperation(exportParams, result.ConnectionInfo); // set cancellation token to cancel operation.Cancel(); @@ -216,6 +212,54 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx return requestContext; } + private async Task>> SendAndValidateGenerateDeployScriptRequest() + { + // first extract a dacpac + var result = GetLiveAutoCompleteTestObjects(); + var extractRequestContext = new Mock>(); + extractRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + + SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxGenerateScriptTest"); + string folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "DacFxTest"); + Directory.CreateDirectory(folderPath); + + var extractParams = new ExtractParams + { + DatabaseName = sourceDb.DatabaseName, + PackageFilePath = Path.Combine(folderPath, string.Format("{0}.dacpac", sourceDb.DatabaseName)), + ApplicationName = "test", + ApplicationVersion = new Version(1, 0) + }; + + DacFxService service = new DacFxService(); + ExtractOperation extractOperation = new ExtractOperation(extractParams, result.ConnectionInfo); + service.PerformOperation(extractOperation); + + // generate script + var generateScriptRequestContext = new Mock>(); + generateScriptRequestContext.Setup(x => x.SendResult(It.IsAny())).Returns(Task.FromResult(new object())); + + + var generateScriptParams = new GenerateDeployScriptParams + { + PackageFilePath = extractParams.PackageFilePath, + DatabaseName = string.Concat(sourceDb.DatabaseName, "-deployed"), + ScriptFilePath = Path.Combine(folderPath, string.Concat(sourceDb.DatabaseName, "_", "UpgradeDACScript.sql")) + }; + + GenerateDeployScriptOperation generateScriptOperation = new GenerateDeployScriptOperation(generateScriptParams, result.ConnectionInfo); + service.PerformOperation(generateScriptOperation); + SqlTestDb targetDb = SqlTestDb.CreateFromExisting(generateScriptParams.DatabaseName); + + // cleanup + VerifyAndCleanup(generateScriptParams.ScriptFilePath); + VerifyAndCleanup(extractParams.PackageFilePath); + sourceDb.Cleanup(); + targetDb.Cleanup(); + + return generateScriptRequestContext; + } + /// /// Verify the export bacpac request /// @@ -261,6 +305,15 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx Assert.NotNull(await SendAndValidateDeployRequest()); } + /// + /// Verify the gnerate deploy script request + /// + [Fact] + public async void GenerateDeployScript() + { + Assert.NotNull(await SendAndValidateGenerateDeployScriptRequest()); + } + private void VerifyAndCleanup(string filePath) { // Verify it was created