Add deploy options support (#11567)

* add getOptionsFromProfile request

* update deploy and generate script to accept options

* fix tests

* update message

* update message to say what isn't supported

* bump sqltoolsservice version
This commit is contained in:
Kim Santiago
2020-07-29 17:52:21 -07:00
committed by GitHub
parent 6751dffbc3
commit c06bd0821b
13 changed files with 249 additions and 22 deletions

View File

@@ -77,7 +77,7 @@ export const selectConnectionRadioButtonsTitle = localize('selectconnectionRadio
export const dataSourceDropdownTitle = localize('dataSourceDropdownTitle', "Data source");
export const noDataSourcesText = localize('noDataSourcesText', "No data sources in this project");
export const loadProfileButtonText = localize('loadProfileButtonText', "Load Profile...");
export const profileWarningText = localize('profileWarningText', "⚠ Warning: Only SQL Login and Integrated Authentication connection strings, database name, and SQLCMD variables are able to be loaded from a profile at this time");
export const profileWarningText = localize('profileWarningText', "⚠ Warning: Connection strings using AAD Authentication are not supported at this time");
export const sqlCmdTableLabel = localize('sqlCmdTableLabel', "SQLCMD Variables");
export const sqlCmdVariableColumn = localize('sqlCmdVariableColumn', "Variable");
export const sqlCmdValueColumn = localize('sqlCmdValueColumn', "Value");

View File

@@ -220,15 +220,16 @@ export class ProjectsController {
const dacFxService = await this.getDaxFxService();
if ((<IPublishSettings>settings).upgradeExisting) {
return await dacFxService.deployDacpac(tempPath, settings.databaseName, (<IPublishSettings>settings).upgradeExisting, settings.connectionUri, azdata.TaskExecutionMode.execute, settings.sqlCmdVariables);
return await dacFxService.deployDacpac(tempPath, settings.databaseName, (<IPublishSettings>settings).upgradeExisting, settings.connectionUri, azdata.TaskExecutionMode.execute, settings.sqlCmdVariables, settings.deploymentOptions);
}
else {
return await dacFxService.generateDeployScript(tempPath, settings.databaseName, settings.connectionUri, azdata.TaskExecutionMode.script, settings.sqlCmdVariables);
return await dacFxService.generateDeployScript(tempPath, settings.databaseName, settings.connectionUri, azdata.TaskExecutionMode.script, settings.sqlCmdVariables, settings.deploymentOptions);
}
}
public async readPublishProfileCallback(profileUri: vscode.Uri): Promise<PublishProfile> {
const profile = await load(profileUri);
const dacFxService = await this.getDaxFxService();
const profile = await load(profileUri, dacFxService);
return profile;
}

View File

@@ -11,6 +11,7 @@ import * as utils from '../common/utils';
import { Project } from '../models/project';
import { SqlConnectionDataSource } from '../models/dataSources/sqlConnectionStringSource';
import { IPublishSettings, IGenerateScriptSettings } from '../models/IPublishSettings';
import { DeploymentOptions } from '../../../mssql/src/mssql';
interface DataSourceDropdownValue extends azdata.CategoryValue {
dataSource: SqlConnectionDataSource;
@@ -35,6 +36,7 @@ export class PublishDatabaseDialog {
private connectionId: string | undefined;
private connectionIsDataSource: boolean | undefined;
private profileSqlCmdVars: Record<string, string> | undefined;
private deploymentOptions: DeploymentOptions | undefined;
private toDispose: vscode.Disposable[] = [];
@@ -191,7 +193,8 @@ export class PublishDatabaseDialog {
databaseName: this.getTargetDatabaseName(),
upgradeExisting: true,
connectionUri: await this.getConnectionUri(),
sqlCmdVariables: sqlCmdVars
sqlCmdVariables: sqlCmdVars,
deploymentOptions: this.deploymentOptions
};
azdata.window.closeDialog(this.dialog);
@@ -205,7 +208,8 @@ export class PublishDatabaseDialog {
const settings: IGenerateScriptSettings = {
databaseName: this.getTargetDatabaseName(),
connectionUri: await this.getConnectionUri(),
sqlCmdVariables: sqlCmdVars
sqlCmdVariables: sqlCmdVars,
deploymentOptions: this.deploymentOptions
};
azdata.window.closeDialog(this.dialog);
@@ -399,7 +403,7 @@ export class PublishDatabaseDialog {
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.Uri.parse(this.project.projectFolderPath),
defaultUri: vscode.workspace.workspaceFolders ? (vscode.workspace.workspaceFolders as vscode.WorkspaceFolder[])[0].uri : undefined,
filters: {
[constants.publishSettingsFiles]: ['publish.xml']
}
@@ -417,6 +421,7 @@ export class PublishDatabaseDialog {
this.connectionId = result.connectionId;
(<azdata.InputBoxComponent>this.targetConnectionTextBox).value = result.connectionString;
this.deploymentOptions = result.options;
this.profileSqlCmdVars = result.sqlCmdVariables;
const data = this.convertSqlCmdVarsToTableFormat(this.getSqlCmdVariablesForPublish());

View File

@@ -3,15 +3,19 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DeploymentOptions } from '../../../mssql/src/mssql';
export interface IPublishSettings {
databaseName: string;
connectionUri: string;
upgradeExisting: boolean;
sqlCmdVariables?: Record<string, string>;
deploymentOptions?: DeploymentOptions;
}
export interface IGenerateScriptSettings {
databaseName: string;
connectionUri: string;
sqlCmdVariables?: Record<string, string>;
deploymentOptions?: DeploymentOptions;
}

View File

@@ -7,6 +7,7 @@ import * as azdata from 'azdata';
import * as xmldom from 'xmldom';
import * as constants from '../../common/constants';
import * as utils from '../../common/utils';
import * as mssql from '../../../../mssql';
import { promises as fs } from 'fs';
import { Uri } from 'vscode';
@@ -18,12 +19,13 @@ export interface PublishProfile {
connectionId: string;
connectionString: string;
sqlCmdVariables: Record<string, string>;
options?: mssql.DeploymentOptions;
}
/**
* parses the specified file to load publish settings
*/
export async function load(profileUri: Uri): Promise<PublishProfile> {
export async function load(profileUri: Uri, dacfxService: mssql.IDacFxService): Promise<PublishProfile> {
const profileText = await fs.readFile(profileUri.fsPath);
const profileXmlDoc = new xmldom.DOMParser().parseFromString(profileText.toString());
@@ -36,6 +38,7 @@ export async function load(profileUri: Uri): Promise<PublishProfile> {
}
const connectionInfo = await readConnectionString(profileXmlDoc);
const optionsResult = await dacfxService.getOptionsFromProfile(profileUri.fsPath);
// get all SQLCMD variables to include from the profile
const sqlCmdVariables = readSqlCmdVariables(profileXmlDoc);
@@ -44,7 +47,8 @@ export async function load(profileUri: Uri): Promise<PublishProfile> {
databaseName: targetDbName,
connectionId: connectionInfo.connectionId,
connectionString: connectionInfo.connectionString,
sqlCmdVariables: sqlCmdVariables
sqlCmdVariables: sqlCmdVariables,
options: optionsResult.deploymentOptions
};
}
@@ -54,7 +58,7 @@ export async function load(profileUri: Uri): Promise<PublishProfile> {
*/
export function readSqlCmdVariables(xmlDoc: any): Record<string, string> {
let sqlCmdVariables: Record<string, string> = {};
for (let i = 0; i < xmlDoc.documentElement.getElementsByTagName(constants.SqlCmdVariable).length; i++) {
for (let i = 0; i < xmlDoc.documentElement.getElementsByTagName(constants.SqlCmdVariable)?.length; i++) {
const sqlCmdVar = xmlDoc.documentElement.getElementsByTagName(constants.SqlCmdVariable)[i];
const varName = sqlCmdVar.getAttribute(constants.Include);

View File

@@ -289,7 +289,7 @@ describe('ProjectsController', function (): void {
let builtDacpacPath = '';
let publishedDacpacPath = '';
testContext.dacFxService.setup(x => x.generateDeployScript(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async (p) => {
testContext.dacFxService.setup(x => x.generateDeployScript(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(async (p) => {
publishedDacpacPath = p;
postCopyContents = (await fs.readFile(publishedDacpacPath)).toString();
return Promise.resolve(mockDacFxResult);

View File

@@ -7,18 +7,26 @@ import * as should from 'should';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as sinon from 'sinon';
import * as TypeMoq from 'typemoq';
import * as baselines from './baselines/baselines';
import * as testUtils from './testUtils';
import * as constants from '../common/constants';
import { ProjectsController } from '../controllers/projectController';
import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProjectTreeViewProvider';
import { TestContext, createContext, mockDacFxOptionsResult } from './testContext';
import { load } from '../models/publishProfile/publishProfile';
let testContext: TestContext;
describe('Publish profile tests', function (): void {
before(async function (): Promise<void> {
await baselines.loadBaselines();
});
beforeEach(function (): void {
testContext = createContext();
});
afterEach(function (): void {
sinon.restore();
});
@@ -26,44 +34,50 @@ describe('Publish profile tests', function (): void {
it('Should read database name, integrated security connection string, and SQLCMD variables from publish profile', async function (): Promise<void> {
await baselines.loadBaselines();
let profilePath = await testUtils.createTestFile(baselines.publishProfileIntegratedSecurityBaseline, 'publishProfile.publish.xml');
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const connectionResult = {
connected: true,
connectionId: 'connId',
errorMessage: '',
errorCode: 0
};
testContext.dacFxService.setup(x => x.getOptionsFromProfile(TypeMoq.It.isAny())).returns(async () => {
return Promise.resolve(mockDacFxOptionsResult);
});
const connectionString = 'Data Source=testserver;Integrated Security=true;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True';
sinon.stub(azdata.connection, 'connect').resolves(connectionResult);
sinon.stub(azdata.connection, 'getConnectionString').resolves(connectionString);
let result = await projController.readPublishProfileCallback(vscode.Uri.file(profilePath));
let result = await load(vscode.Uri.file(profilePath), testContext.dacFxService.object);
should(result.databaseName).equal('targetDb');
should(Object.keys(result.sqlCmdVariables).length).equal(1);
should(result.sqlCmdVariables['ProdDatabaseName']).equal('MyProdDatabase');
should(result.connectionId).equal('connId');
should(result.connectionString).equal('Data Source=testserver;Integrated Security=true;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True');
should(result.options).equal(mockDacFxOptionsResult.deploymentOptions);
});
it('Should read database name, SQL login connection string, and SQLCMD variables from publish profile', async function (): Promise<void> {
await baselines.loadBaselines();
let profilePath = await testUtils.createTestFile(baselines.publishProfileSqlLoginBaseline, 'publishProfile.publish.xml');
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const connectionResult = {
providerName: 'MSSQL',
connectionId: 'connId',
options: {}
};
testContext.dacFxService.setup(x => x.getOptionsFromProfile(TypeMoq.It.isAny())).returns(async () => {
return Promise.resolve(mockDacFxOptionsResult);
});
const connectionString = 'Data Source=testserver;User Id=testUser;Password=******;Integrated Security=false;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True';
sinon.stub(azdata.connection, 'openConnectionDialog').resolves(connectionResult);
sinon.stub(azdata.connection, 'getConnectionString').resolves(connectionString);
let result = await projController.readPublishProfileCallback(vscode.Uri.file(profilePath));
let result = await load(vscode.Uri.file(profilePath), testContext.dacFxService.object);
should(result.databaseName).equal('targetDb');
should(Object.keys(result.sqlCmdVariables).length).equal(1);
should(result.sqlCmdVariables['ProdDatabaseName']).equal('MyProdDatabase');
should(result.connectionId).equal('connId');
should(result.connectionString).equal('Data Source=testserver;User Id=testUser;Password=******;Integrated Security=false;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True');
should(result.options).equal(mockDacFxOptionsResult.deploymentOptions);
});
it('Should throw error when connecting does not work', async function (): Promise<void> {

View File

@@ -21,6 +21,91 @@ export const mockDacFxResult = {
report: ''
};
export const mockDacFxOptionsResult: mssql.DacFxOptionsResult = {
success: true,
errorMessage: '',
deploymentOptions: {
ignoreTableOptions: false,
ignoreSemicolonBetweenStatements: false,
ignoreRouteLifetime: false,
ignoreRoleMembership: false,
ignoreQuotedIdentifiers: false,
ignorePermissions: false,
ignorePartitionSchemes: false,
ignoreObjectPlacementOnPartitionScheme: false,
ignoreNotForReplication: false,
ignoreLoginSids: false,
ignoreLockHintsOnIndexes: false,
ignoreKeywordCasing: false,
ignoreIndexPadding: false,
ignoreIndexOptions: false,
ignoreIncrement: false,
ignoreIdentitySeed: false,
ignoreUserSettingsObjects: false,
ignoreFullTextCatalogFilePath: false,
ignoreWhitespace: false,
ignoreWithNocheckOnForeignKeys: false,
verifyCollationCompatibility: false,
unmodifiableObjectWarnings: false,
treatVerificationErrorsAsWarnings: false,
scriptRefreshModule: false,
scriptNewConstraintValidation: false,
scriptFileSize: false,
scriptDeployStateChecks: false,
scriptDatabaseOptions: false,
scriptDatabaseCompatibility: false,
scriptDatabaseCollation: false,
runDeploymentPlanExecutors: false,
registerDataTierApplication: false,
populateFilesOnFileGroups: false,
noAlterStatementsToChangeClrTypes: false,
includeTransactionalScripts: false,
includeCompositeObjects: false,
allowUnsafeRowLevelSecurityDataMovement: false,
ignoreWithNocheckOnCheckConstraints: false,
ignoreFillFactor: false,
ignoreFileSize: false,
ignoreFilegroupPlacement: false,
doNotAlterReplicatedObjects: false,
doNotAlterChangeDataCaptureObjects: false,
disableAndReenableDdlTriggers: false,
deployDatabaseInSingleUserMode: false,
createNewDatabase: false,
compareUsingTargetCollation: false,
commentOutSetVarDeclarations: false,
blockWhenDriftDetected: false,
blockOnPossibleDataLoss: false,
backupDatabaseBeforeChanges: false,
allowIncompatiblePlatform: false,
allowDropBlockingAssemblies: false,
dropConstraintsNotInSource: false,
dropDmlTriggersNotInSource: false,
dropExtendedPropertiesNotInSource: false,
dropIndexesNotInSource: false,
ignoreFileAndLogFilePath: false,
ignoreExtendedProperties: false,
ignoreDmlTriggerState: false,
ignoreDmlTriggerOrder: false,
ignoreDefaultSchema: false,
ignoreDdlTriggerState: false,
ignoreDdlTriggerOrder: false,
ignoreCryptographicProviderFilePath: false,
verifyDeployment: false,
ignoreComments: false,
ignoreColumnCollation: false,
ignoreAuthorizer: false,
ignoreAnsiNulls: false,
generateSmartDefaults: false,
dropStatisticsNotInSource: false,
dropRoleMembersNotInSource: false,
dropPermissionsNotInSource: false,
dropObjectsNotInSource: false,
ignoreColumnOrder: false,
doNotDropObjectTypes: [],
excludeObjectTypes: []
}
};
export class MockDacFxService implements mssql.IDacFxService {
public exportBacpac(_: string, __: string, ___: string, ____: azdata.TaskExecutionMode): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
public importBacpac(_: string, __: string, ___: string, ____: azdata.TaskExecutionMode): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
@@ -29,6 +114,7 @@ export class MockDacFxService implements mssql.IDacFxService {
public deployDacpac(_: string, __: string, ___: boolean, ____: string, _____: azdata.TaskExecutionMode, ______?: Record<string, string>): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
public generateDeployScript(_: string, __: string, ___: string, ____: azdata.TaskExecutionMode, ______?: Record<string, string>): Thenable<mssql.DacFxResult> { return Promise.resolve(mockDacFxResult); }
public generateDeployPlan(_: string, __: string, ___: string, ____: azdata.TaskExecutionMode): Thenable<mssql.GenerateDeployPlanResult> { return Promise.resolve(mockDacFxResult); }
public getOptionsFromProfile(_: string): Thenable<mssql.DacFxOptionsResult> { return Promise.resolve(mockDacFxOptionsResult); }
}
export function createContext(): TestContext {