mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 09:35:37 -05:00
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:
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user