Add DacFx Generate Deploy Plan Operation (#768)

* initial adding upgrade plan

* upgrade plan request from ADS works

* now data loss operations are returned

* fixes after rebasing

* refactoring to use GenerateDeployPlan instead of UpgradePlan

* improving test

* Addressing comments

* changing abstract execute to virtual

* changed GenerateDeployPlanOepration to use Execute()

* addressing comments

* simplyfing deploy options

* adding deploy options to deploy and generate script operations
This commit is contained in:
kisantia
2019-02-06 17:22:20 -08:00
committed by GitHub
parent 9177a6be8b
commit 0a172f3c8e
7 changed files with 211 additions and 6 deletions

View File

@@ -0,0 +1,37 @@
//
// 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
{
/// <summary>
/// Parameters for a DacFx generate deploy plan request.
/// </summary>
public class GenerateDeployPlanParams : DacFxParams
{
}
/// <summary>
/// Defines the DacFx generate deploy plan request type
/// </summary>
class GenerateDeployPlanRequest
{
public static readonly RequestType<GenerateDeployPlanParams, GenerateDeployPlanRequestResult> Type =
RequestType<GenerateDeployPlanParams, GenerateDeployPlanRequestResult>.Create("dacfx/generateDeployPlan");
}
/// <summary>
/// Parameters returned from a generate deploy script request.
/// </summary>
public class GenerateDeployPlanRequestResult : DacFxResult
{
/// <summary>
/// An xml string that details the alerts and the operations for deploying the specified dacpac to the database
/// </summary>
public string Report { get; set; }
}
}

View File

@@ -90,5 +90,23 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
}
public abstract void Execute();
protected DacDeployOptions GetDefaultDeployOptions()
{
DacDeployOptions options = new DacDeployOptions
{
AllowDropBlockingAssemblies = true,
AllowIncompatiblePlatform = true,
BlockOnPossibleDataLoss = false,
DropObjectsNotInSource = true,
DropPermissionsNotInSource = true,
DropRoleMembersNotInSource = true,
IgnoreKeywordCasing = false,
IgnoreSemicolonBetweenStatements = false,
IgnoreWhitespace = false,
};
return options;
}
}
}

View File

@@ -44,6 +44,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
serviceHost.SetRequestHandler(ExtractRequest.Type, this.HandleExtractRequest);
serviceHost.SetRequestHandler(DeployRequest.Type, this.HandleDeployRequest);
serviceHost.SetRequestHandler(GenerateDeployScriptRequest.Type, this.HandleGenerateDeployScriptRequest);
serviceHost.SetRequestHandler(GenerateDeployPlanRequest.Type, this.HandleGenerateDeployPlanRequest);
}
/// <summary>
@@ -167,7 +168,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
// want to show filepath in task history instead of server and database
metadata.ServerName = parameters.ScriptFilePath;
metadata.DatabaseName = "";
metadata.DatabaseName = string.Empty;
sqlTask = SqlTaskManagerInstance.CreateAndRun<SqlTask>(metadata);
@@ -175,7 +176,39 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
{
OperationId = operation.OperationId,
Success = true,
ErrorMessage = ""
ErrorMessage = string.Empty
});
}
}
catch (Exception e)
{
await requestContext.SendError(e);
}
}
/// <summary>
/// Handles request to generate deploy plan
/// </summary>
/// <returns></returns>
public async Task HandleGenerateDeployPlanRequest(GenerateDeployPlanParams parameters, RequestContext<GenerateDeployPlanRequestResult> requestContext)
{
try
{
ConnectionInfo connInfo;
ConnectionServiceInstance.TryFindConnection(
parameters.OwnerUri,
out connInfo);
if (connInfo != null)
{
GenerateDeployPlanOperation operation = new GenerateDeployPlanOperation(parameters, connInfo);
operation.Execute(parameters.TaskExecutionMode);
await requestContext.SendResult(new GenerateDeployPlanRequestResult()
{
OperationId = operation.OperationId,
Success = true,
ErrorMessage = string.Empty,
Report = operation.DeployReport
});
}
}
@@ -199,7 +232,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
{
OperationId = operation.OperationId,
Success = true,
ErrorMessage = ""
ErrorMessage = string.Empty
});
}

View File

@@ -29,7 +29,8 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
public override void Execute()
{
DacPackage dacpac = DacPackage.Load(this.Parameters.PackageFilePath);
this.DacServices.Deploy(dacpac, this.Parameters.DatabaseName, this.Parameters.UpgradeExisting, null, this.CancellationToken);
DacDeployOptions options = this.GetDefaultDeployOptions();
this.DacServices.Deploy(dacpac, this.Parameters.DatabaseName, this.Parameters.UpgradeExisting, options, this.CancellationToken);
}
}
}

View File

@@ -0,0 +1,38 @@
//
// 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;
namespace Microsoft.SqlTools.ServiceLayer.DacFx
{
/// <summary>
/// Class to represent an in-progress generate deploy plan operation
/// </summary>
class GenerateDeployPlanOperation : DacFxOperation
{
public GenerateDeployPlanParams Parameters { get; }
public string DeployReport { get; set; }
public GenerateDeployPlanOperation(GenerateDeployPlanParams parameters, ConnectionInfo connInfo): base(connInfo)
{
Validate.IsNotNull("parameters", parameters);
this.Parameters = parameters;
}
public override void Execute()
{
DacPackage dacpac = DacPackage.Load(this.Parameters.PackageFilePath);
DacDeployOptions options = GetDefaultDeployOptions();
DeployReport = this.DacServices.GenerateDeployReport(dacpac, this.Parameters.DatabaseName, options, this.CancellationToken);
}
}
}

View File

@@ -33,6 +33,7 @@ namespace Microsoft.SqlTools.ServiceLayer.DacFx
PublishOptions publishOptions = new PublishOptions();
publishOptions.GenerateDeploymentReport = this.Parameters.GenerateDeploymentReport;
publishOptions.CancelToken = this.CancellationToken;
publishOptions.DeployOptions = this.GetDefaultDeployOptions();
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)));

View File

@@ -199,7 +199,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx
{
service.PerformOperation(operation);
}
catch(OperationCanceledException canceledException)
catch (OperationCanceledException canceledException)
{
expectedException = canceledException;
}
@@ -260,6 +260,74 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx
return generateScriptRequestContext;
}
private async Task<Mock<RequestContext<DacFxResult>>> SendAndValidateGenerateDeployPlanRequest()
{
var result = GetLiveAutoCompleteTestObjects();
SqlTestDb sourceDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxGenerateDeployPlanTest");
sourceDb.RunQuery(@"CREATE TABLE [dbo].[table1]
(
[ID] INT NOT NULL PRIMARY KEY,
[Date] DATE NOT NULL
)
CREATE TABLE [dbo].[table2]
(
[ID] INT NOT NULL PRIMARY KEY,
[col1] NCHAR(10) NULL
)");
DacFxService service = new DacFxService();
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)
};
ExtractOperation extractOperation = new ExtractOperation(extractParams, result.ConnectionInfo);
service.PerformOperation(extractOperation);
// generate deploy plan for deploying dacpac to targetDb
var generateDeployPlanRequestContext = new Mock<RequestContext<DacFxResult>>();
generateDeployPlanRequestContext.Setup(x => x.SendResult(It.IsAny<DacFxResult>())).Returns(Task.FromResult(new object()));
SqlTestDb targetDb = await SqlTestDb.CreateNewAsync(TestServerType.OnPrem, false, null, null, "DacFxGenerateDeployPlanTestTarget");
targetDb.RunQuery(@"CREATE TABLE [dbo].[table2]
(
[ID] INT NOT NULL PRIMARY KEY,
[col1] NCHAR(10) NULL,
[col2] NCHAR(10) NULL
)
CREATE TABLE [dbo].[table3]
(
[ID] INT NOT NULL PRIMARY KEY,
[col1] INT NULL,
)");
var generateDeployPlanParams = new GenerateDeployPlanParams
{
PackageFilePath = extractParams.PackageFilePath,
DatabaseName = targetDb.DatabaseName,
};
GenerateDeployPlanOperation generateDeployPlanOperation = new GenerateDeployPlanOperation(generateDeployPlanParams, result.ConnectionInfo);
service.PerformOperation(generateDeployPlanOperation);
string report = generateDeployPlanOperation.DeployReport;
Assert.NotNull(report);
Assert.Contains("Create", report);
Assert.Contains("Drop", report);
Assert.Contains("Alter", report);
// cleanup
VerifyAndCleanup(extractParams.PackageFilePath);
sourceDb.Cleanup();
targetDb.Cleanup();
return generateDeployPlanRequestContext;
}
/// <summary>
/// Verify the export bacpac request
/// </summary>
@@ -306,7 +374,7 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx
}
/// <summary>
/// Verify the gnerate deploy script request
/// Verify the generate deploy script request
/// </summary>
[Fact]
public async void GenerateDeployScript()
@@ -314,6 +382,15 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.DacFx
Assert.NotNull(await SendAndValidateGenerateDeployScriptRequest());
}
/// <summary>
/// Verify the generate deploy plan request
/// </summary>
[Fact]
public async void GenerateDeployPlan()
{
Assert.NotNull(await SendAndValidateGenerateDeployPlanRequest());
}
private void VerifyAndCleanup(string filePath)
{
// Verify it was created