mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Load connection from publish profile (#11263)
* initial changes for reading connection from profile * connection string can now be read from publish.xml * fix build errors and update test * move publish profile tests to their own file * cleanup * update message * fix string * remove apiWrapper
This commit is contained in:
@@ -77,7 +77,7 @@ export const selectConnectionRadioButtonsTitle = localize('selectconnectionRadio
|
|||||||
export const dataSourceDropdownTitle = localize('dataSourceDropdownTitle', "Data source");
|
export const dataSourceDropdownTitle = localize('dataSourceDropdownTitle', "Data source");
|
||||||
export const noDataSourcesText = localize('noDataSourcesText', "No data sources in this project");
|
export const noDataSourcesText = localize('noDataSourcesText', "No data sources in this project");
|
||||||
export const loadProfileButtonText = localize('loadProfileButtonText', "Load Profile...");
|
export const loadProfileButtonText = localize('loadProfileButtonText', "Load Profile...");
|
||||||
export const profileWarningText = localize('profileWarningText', "⚠Warning: Only database name and SQLCMD variables are able to be loaded from a profile at this time");
|
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 sqlCmdTableLabel = localize('sqlCmdTableLabel', "SQLCMD Variables");
|
export const sqlCmdTableLabel = localize('sqlCmdTableLabel', "SQLCMD Variables");
|
||||||
export const sqlCmdVariableColumn = localize('sqlCmdVariableColumn', "Variable");
|
export const sqlCmdVariableColumn = localize('sqlCmdVariableColumn', "Variable");
|
||||||
export const sqlCmdValueColumn = localize('sqlCmdValueColumn', "Value");
|
export const sqlCmdValueColumn = localize('sqlCmdValueColumn', "Value");
|
||||||
@@ -107,7 +107,6 @@ export const databaseNameRequired = localize('databaseNameRequired', "Database n
|
|||||||
export const invalidDataSchemaProvider = localize('invalidDataSchemaProvider', "Invalid DSP in .sqlproj file");
|
export const invalidDataSchemaProvider = localize('invalidDataSchemaProvider', "Invalid DSP in .sqlproj file");
|
||||||
export const invalidDatabaseReference = localize('invalidDatabaseReference', "Invalid database reference in .sqlproj file");
|
export const invalidDatabaseReference = localize('invalidDatabaseReference', "Invalid database reference in .sqlproj file");
|
||||||
export const databaseSelectionRequired = localize('databaseSelectionRequired', "Database selection is required to import a project");
|
export const databaseSelectionRequired = localize('databaseSelectionRequired', "Database selection is required to import a project");
|
||||||
export const unableToCreatePublishConnection = localize('unableToCreatePublishConnection', "Unable to construct connection");
|
|
||||||
export const databaseReferenceAlreadyExists = localize('databaseReferenceAlreadyExists', "A reference to this database already exists in this project");
|
export const databaseReferenceAlreadyExists = localize('databaseReferenceAlreadyExists', "A reference to this database already exists in this project");
|
||||||
export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); }
|
export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); }
|
||||||
export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); }
|
export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); }
|
||||||
@@ -116,6 +115,7 @@ export function cannotResolvePath(path: string) { return localize('cannotResolve
|
|||||||
export function fileAlreadyExists(filename: string) { return localize('fileAlreadyExists', "A file with the name '{0}' already exists on disk at this location. Please choose another name.", filename); }
|
export function fileAlreadyExists(filename: string) { return localize('fileAlreadyExists', "A file with the name '{0}' already exists on disk at this location. Please choose another name.", filename); }
|
||||||
export function folderAlreadyExists(filename: string) { return localize('folderAlreadyExists', "A folder with the name '{0}' already exists on disk at this location. Please choose another name.", filename); }
|
export function folderAlreadyExists(filename: string) { return localize('folderAlreadyExists', "A folder with the name '{0}' already exists on disk at this location. Please choose another name.", filename); }
|
||||||
export function invalidInput(input: string) { return localize('invalidInput', "Invalid input: {0}", input); }
|
export function invalidInput(input: string) { return localize('invalidInput', "Invalid input: {0}", input); }
|
||||||
|
export function unableToCreatePublishConnection(input: string) { return localize('unableToCreatePublishConnection', "Unable to construct connection: {0}", input); }
|
||||||
|
|
||||||
export function mssqlNotFound(mssqlConfigDir: string) { return localize('mssqlNotFound', "Could not get mssql extension's install location at {0}", mssqlConfigDir); }
|
export function mssqlNotFound(mssqlConfigDir: string) { return localize('mssqlNotFound', "Could not get mssql extension's install location at {0}", mssqlConfigDir); }
|
||||||
export function projBuildFailed(errorMessage: string) { return localize('projBuildFailed', "Build failed. Check output pane for more details. {0}", errorMessage); }
|
export function projBuildFailed(errorMessage: string) { return localize('projBuildFailed', "Build failed. Check output pane for more details. {0}", errorMessage); }
|
||||||
@@ -183,6 +183,7 @@ export const All = 'All';
|
|||||||
|
|
||||||
// Profile XML names
|
// Profile XML names
|
||||||
export const targetDatabaseName = 'TargetDatabaseName';
|
export const targetDatabaseName = 'TargetDatabaseName';
|
||||||
|
export const targetConnectionString = 'TargetConnectionString';
|
||||||
|
|
||||||
// SQL connection string components
|
// SQL connection string components
|
||||||
export const initialCatalogSetting = 'Initial Catalog';
|
export const initialCatalogSetting = 'Initial Catalog';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as constants from '../common/constants';
|
import * as constants from './constants';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import * as path from 'path';
|
|||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||||
import * as templates from '../templates/templates';
|
import * as templates from '../templates/templates';
|
||||||
import * as xmldom from 'xmldom';
|
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
@@ -20,12 +19,13 @@ import { PublishDatabaseDialog } from '../dialogs/publishDatabaseDialog';
|
|||||||
import { Project, DatabaseReferenceLocation, SystemDatabase, TargetPlatform, ProjectEntry, reservedProjectFolders } from '../models/project';
|
import { Project, DatabaseReferenceLocation, SystemDatabase, TargetPlatform, ProjectEntry, reservedProjectFolders } from '../models/project';
|
||||||
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
|
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
|
||||||
import { FolderNode, FileNode } from '../models/tree/fileFolderTreeItem';
|
import { FolderNode, FileNode } from '../models/tree/fileFolderTreeItem';
|
||||||
import { IPublishSettings, IGenerateScriptSettings, PublishProfile } from '../models/IPublishSettings';
|
import { IPublishSettings, IGenerateScriptSettings } from '../models/IPublishSettings';
|
||||||
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
|
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
|
||||||
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
|
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
|
||||||
import { ImportDataModel } from '../models/api/import';
|
import { ImportDataModel } from '../models/api/import';
|
||||||
import { NetCoreTool, DotNetCommandOptions } from '../tools/netcoreTool';
|
import { NetCoreTool, DotNetCommandOptions } from '../tools/netcoreTool';
|
||||||
import { BuildHelper } from '../tools/buildHelper';
|
import { BuildHelper } from '../tools/buildHelper';
|
||||||
|
import { PublishProfile, load } from '../models/publishProfile/publishProfile';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for managing project lifecycle
|
* Controller for managing project lifecycle
|
||||||
@@ -199,7 +199,7 @@ export class ProjectsController {
|
|||||||
|
|
||||||
publishDatabaseDialog.publish = async (proj, prof) => await this.executionCallback(proj, prof);
|
publishDatabaseDialog.publish = async (proj, prof) => await this.executionCallback(proj, prof);
|
||||||
publishDatabaseDialog.generateScript = async (proj, prof) => await this.executionCallback(proj, prof);
|
publishDatabaseDialog.generateScript = async (proj, prof) => await this.executionCallback(proj, prof);
|
||||||
publishDatabaseDialog.readPublishProfile = async (profileUri) => await this.readPublishProfile(profileUri);
|
publishDatabaseDialog.readPublishProfile = async (profileUri) => await this.readPublishProfileCallback(profileUri);
|
||||||
|
|
||||||
publishDatabaseDialog.openDialog();
|
publishDatabaseDialog.openDialog();
|
||||||
|
|
||||||
@@ -227,25 +227,9 @@ export class ProjectsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async readPublishProfile(profileUri: vscode.Uri): Promise<PublishProfile> {
|
public async readPublishProfileCallback(profileUri: vscode.Uri): Promise<PublishProfile> {
|
||||||
const profileText = await fs.readFile(profileUri.fsPath);
|
const profile = await load(profileUri);
|
||||||
const profileXmlDoc = new xmldom.DOMParser().parseFromString(profileText.toString());
|
return profile;
|
||||||
|
|
||||||
// read target database name
|
|
||||||
let targetDbName: string = '';
|
|
||||||
let targetDatabaseNameCount = profileXmlDoc.documentElement.getElementsByTagName(constants.targetDatabaseName).length;
|
|
||||||
if (targetDatabaseNameCount > 0) {
|
|
||||||
// if there is more than one TargetDatabaseName nodes, SSDT uses the name in the last one so we'll do the same here
|
|
||||||
targetDbName = profileXmlDoc.documentElement.getElementsByTagName(constants.targetDatabaseName)[targetDatabaseNameCount - 1].textContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all SQLCMD variables to include from the profile
|
|
||||||
let sqlCmdVariables = utils.readSqlCmdVariables(profileXmlDoc);
|
|
||||||
|
|
||||||
return {
|
|
||||||
databaseName: targetDbName,
|
|
||||||
sqlCmdVariables: sqlCmdVariables
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async schemaCompare(treeNode: BaseProjectTreeItem): Promise<void> {
|
public async schemaCompare(treeNode: BaseProjectTreeItem): Promise<void> {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export class PublishDatabaseDialog {
|
|||||||
private sqlCmdVariablesTable: azdata.TableComponent | undefined;
|
private sqlCmdVariablesTable: azdata.TableComponent | undefined;
|
||||||
private formBuilder: azdata.FormBuilder | undefined;
|
private formBuilder: azdata.FormBuilder | undefined;
|
||||||
|
|
||||||
private connection: azdata.connection.Connection | undefined;
|
private connectionId: string | undefined;
|
||||||
private connectionIsDataSource: boolean | undefined;
|
private connectionIsDataSource: boolean | undefined;
|
||||||
private profileSqlCmdVars: Record<string, string> | undefined;
|
private profileSqlCmdVars: Record<string, string> | undefined;
|
||||||
|
|
||||||
@@ -155,20 +155,7 @@ export class PublishDatabaseDialog {
|
|||||||
|
|
||||||
if (this.connectionIsDataSource) {
|
if (this.connectionIsDataSource) {
|
||||||
const dataSource = (this.dataSourcesDropDown!.value! as DataSourceDropdownValue).dataSource;
|
const dataSource = (this.dataSourcesDropDown!.value! as DataSourceDropdownValue).dataSource;
|
||||||
|
const connProfile: azdata.IConnectionProfile = dataSource.getConnectionProfile();
|
||||||
const connProfile: azdata.IConnectionProfile = {
|
|
||||||
serverName: dataSource.server,
|
|
||||||
databaseName: dataSource.database,
|
|
||||||
connectionName: dataSource.name,
|
|
||||||
userName: dataSource.username,
|
|
||||||
password: dataSource.password,
|
|
||||||
authenticationType: dataSource.integratedSecurity ? 'Integrated' : 'SqlAuth',
|
|
||||||
savePassword: false,
|
|
||||||
providerName: 'MSSQL',
|
|
||||||
saveProfile: true,
|
|
||||||
id: dataSource.name + '-dataSource',
|
|
||||||
options: []
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dataSource.integratedSecurity) {
|
if (dataSource.integratedSecurity) {
|
||||||
connId = (await azdata.connection.connect(connProfile, false, false)).connectionId;
|
connId = (await azdata.connection.connect(connProfile, false, false)).connectionId;
|
||||||
@@ -178,11 +165,11 @@ export class PublishDatabaseDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!this.connection) {
|
if (!this.connectionId) {
|
||||||
throw new Error('Connection not defined.');
|
throw new Error('Connection not defined.');
|
||||||
}
|
}
|
||||||
|
|
||||||
connId = this.connection?.connectionId;
|
connId = this.connectionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await azdata.connection.getUriForConnection(connId);
|
return await azdata.connection.getUriForConnection(connId);
|
||||||
@@ -359,18 +346,19 @@ export class PublishDatabaseDialog {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
editConnectionButton.onDidClick(async () => {
|
editConnectionButton.onDidClick(async () => {
|
||||||
this.connection = await azdata.connection.openConnectionDialog();
|
let connection = await azdata.connection.openConnectionDialog();
|
||||||
|
this.connectionId = connection.connectionId;
|
||||||
|
|
||||||
// show connection name if there is one, otherwise show connection string
|
// show connection name if there is one, otherwise show connection string
|
||||||
if (this.connection.options['connectionName']) {
|
if (connection.options['connectionName']) {
|
||||||
this.targetConnectionTextBox!.value = this.connection.options['connectionName'];
|
this.targetConnectionTextBox!.value = connection.options['connectionName'];
|
||||||
} else {
|
} else {
|
||||||
this.targetConnectionTextBox!.value = await azdata.connection.getConnectionString(this.connection.connectionId, false);
|
this.targetConnectionTextBox!.value = await azdata.connection.getConnectionString(connection.connectionId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// change the database inputbox value to the connection's database if there is one
|
// change the database inputbox value to the connection's database if there is one
|
||||||
if (this.connection.options.database && this.connection.options.database !== constants.master) {
|
if (connection.options.database && connection.options.database !== constants.master) {
|
||||||
this.targetDatabaseTextBox!.value = this.connection.options.database;
|
this.targetDatabaseTextBox!.value = connection.options.database;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -419,6 +407,10 @@ export class PublishDatabaseDialog {
|
|||||||
if (this.readPublishProfile) {
|
if (this.readPublishProfile) {
|
||||||
const result = await this.readPublishProfile(fileUris[0]);
|
const result = await this.readPublishProfile(fileUris[0]);
|
||||||
(<azdata.InputBoxComponent>this.targetDatabaseTextBox).value = result.databaseName;
|
(<azdata.InputBoxComponent>this.targetDatabaseTextBox).value = result.databaseName;
|
||||||
|
|
||||||
|
this.connectionId = result.connectionId;
|
||||||
|
(<azdata.InputBoxComponent>this.targetConnectionTextBox).value = result.connectionString;
|
||||||
|
|
||||||
this.profileSqlCmdVars = result.sqlCmdVariables;
|
this.profileSqlCmdVars = result.sqlCmdVariables;
|
||||||
const data = this.convertSqlCmdVarsToTableFormat(this.getSqlCmdVariablesForPublish());
|
const data = this.convertSqlCmdVarsToTableFormat(this.getSqlCmdVariablesForPublish());
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,3 @@ export interface IGenerateScriptSettings {
|
|||||||
connectionUri: string;
|
connectionUri: string;
|
||||||
sqlCmdVariables?: Record<string, string>;
|
sqlCmdVariables?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only reading db name and SQLCMD vars from profile for now
|
|
||||||
export interface PublishProfile {
|
|
||||||
databaseName: string;
|
|
||||||
sqlCmdVariables: Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as azdata from 'azdata';
|
||||||
import { DataSource } from './dataSources';
|
import { DataSource } from './dataSources';
|
||||||
import * as constants from '../../common/constants';
|
import * as constants from '../../common/constants';
|
||||||
|
|
||||||
@@ -70,6 +71,24 @@ export class SqlConnectionDataSource extends DataSource {
|
|||||||
public static fromJson(json: DataSourceJson): SqlConnectionDataSource {
|
public static fromJson(json: DataSourceJson): SqlConnectionDataSource {
|
||||||
return new SqlConnectionDataSource(json.name, (json.data as unknown as SqlConnectionDataSourceJson).connectionString);
|
return new SqlConnectionDataSource(json.name, (json.data as unknown as SqlConnectionDataSourceJson).connectionString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getConnectionProfile(): azdata.IConnectionProfile {
|
||||||
|
const connProfile: azdata.IConnectionProfile = {
|
||||||
|
serverName: this.server,
|
||||||
|
databaseName: this.database,
|
||||||
|
connectionName: this.name,
|
||||||
|
userName: this.username,
|
||||||
|
password: this.password,
|
||||||
|
authenticationType: this.integratedSecurity ? 'Integrated' : 'SqlAuth',
|
||||||
|
savePassword: false,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
saveProfile: true,
|
||||||
|
id: this.name + '-dataSource',
|
||||||
|
options: []
|
||||||
|
};
|
||||||
|
|
||||||
|
return connProfile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import * as os from 'os';
|
|||||||
import { Uri } from 'vscode';
|
import { Uri } from 'vscode';
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { DataSource } from './dataSources/dataSources';
|
import { DataSource } from './dataSources/dataSources';
|
||||||
|
import { readSqlCmdVariables } from './publishProfile/publishProfile';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class representing a Project, and providing functions for operating on it
|
* Class representing a Project, and providing functions for operating on it
|
||||||
@@ -80,7 +81,7 @@ export class Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// find all SQLCMD variables to include
|
// find all SQLCMD variables to include
|
||||||
this.sqlCmdVariables = utils.readSqlCmdVariables(this.projFileXmlDoc);
|
this.sqlCmdVariables = readSqlCmdVariables(this.projFileXmlDoc);
|
||||||
|
|
||||||
// find all database references to include
|
// find all database references to include
|
||||||
const references = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ArtifactReference);
|
const references = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ArtifactReference);
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as xmldom from 'xmldom';
|
||||||
|
import * as constants from '../../common/constants';
|
||||||
|
import * as utils from '../../common/utils';
|
||||||
|
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { Uri } from 'vscode';
|
||||||
|
import { SqlConnectionDataSource } from '../dataSources/sqlConnectionStringSource';
|
||||||
|
|
||||||
|
// only reading db name, connection string, and SQLCMD vars from profile for now
|
||||||
|
export interface PublishProfile {
|
||||||
|
databaseName: string;
|
||||||
|
connectionId: string;
|
||||||
|
connectionString: string;
|
||||||
|
sqlCmdVariables: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parses the specified file to load publish settings
|
||||||
|
*/
|
||||||
|
export async function load(profileUri: Uri): Promise<PublishProfile> {
|
||||||
|
const profileText = await fs.readFile(profileUri.fsPath);
|
||||||
|
const profileXmlDoc = new xmldom.DOMParser().parseFromString(profileText.toString());
|
||||||
|
|
||||||
|
// read target database name
|
||||||
|
let targetDbName: string = '';
|
||||||
|
let targetDatabaseNameCount = profileXmlDoc.documentElement.getElementsByTagName(constants.targetDatabaseName).length;
|
||||||
|
if (targetDatabaseNameCount > 0) {
|
||||||
|
// if there is more than one TargetDatabaseName nodes, SSDT uses the name in the last one so we'll do the same here
|
||||||
|
targetDbName = profileXmlDoc.documentElement.getElementsByTagName(constants.targetDatabaseName)[targetDatabaseNameCount - 1].textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectionInfo = await readConnectionString(profileXmlDoc);
|
||||||
|
|
||||||
|
// get all SQLCMD variables to include from the profile
|
||||||
|
const sqlCmdVariables = readSqlCmdVariables(profileXmlDoc);
|
||||||
|
|
||||||
|
return {
|
||||||
|
databaseName: targetDbName,
|
||||||
|
connectionId: connectionInfo.connectionId,
|
||||||
|
connectionString: connectionInfo.connectionString,
|
||||||
|
sqlCmdVariables: sqlCmdVariables
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read SQLCMD variables from xmlDoc and return them
|
||||||
|
* @param xmlDoc xml doc to read SQLCMD variables from. Format must be the same that sqlproj and publish profiles use
|
||||||
|
*/
|
||||||
|
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++) {
|
||||||
|
const sqlCmdVar = xmlDoc.documentElement.getElementsByTagName(constants.SqlCmdVariable)[i];
|
||||||
|
const varName = sqlCmdVar.getAttribute(constants.Include);
|
||||||
|
|
||||||
|
const varValue = sqlCmdVar.getElementsByTagName(constants.DefaultValue)[0].childNodes[0].nodeValue;
|
||||||
|
sqlCmdVariables[varName] = varValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlCmdVariables;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readConnectionString(xmlDoc: any): Promise<{ connectionId: string, connectionString: string }> {
|
||||||
|
let targetConnectionString: string = '';
|
||||||
|
let connId: string = '';
|
||||||
|
|
||||||
|
if (xmlDoc.documentElement.getElementsByTagName('TargetConnectionString').length > 0) {
|
||||||
|
targetConnectionString = xmlDoc.documentElement.getElementsByTagName('TargetConnectionString')[0].textContent;
|
||||||
|
const dataSource = new SqlConnectionDataSource('temp', targetConnectionString);
|
||||||
|
const connectionProfile = dataSource.getConnectionProfile();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (dataSource.integratedSecurity) {
|
||||||
|
connId = (await azdata.connection.connect(connectionProfile, false, false)).connectionId;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
connId = (await azdata.connection.openConnectionDialog(undefined, connectionProfile)).connectionId;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(constants.unableToCreatePublishConnection(utils.getErrorMessage(err)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mask password in connection string
|
||||||
|
targetConnectionString = await azdata.connection.getConnectionString(connId, false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
connectionId: connId,
|
||||||
|
connectionString: targetConnectionString
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -16,7 +16,8 @@ export let SSDTUpdatedProjectBaseline: string;
|
|||||||
export let SSDTUpdatedProjectAfterSystemDbUpdateBaseline: string;
|
export let SSDTUpdatedProjectAfterSystemDbUpdateBaseline: string;
|
||||||
export let SSDTProjectBaselineWithCleanTarget: string;
|
export let SSDTProjectBaselineWithCleanTarget: string;
|
||||||
export let SSDTProjectBaselineWithCleanTargetAfterUpdate: string;
|
export let SSDTProjectBaselineWithCleanTargetAfterUpdate: string;
|
||||||
export let publishProfileBaseline: string;
|
export let publishProfileIntegratedSecurityBaseline: string;
|
||||||
|
export let publishProfileSqlLoginBaseline: string;
|
||||||
|
|
||||||
const baselineFolderPath = __dirname;
|
const baselineFolderPath = __dirname;
|
||||||
|
|
||||||
@@ -30,7 +31,8 @@ export async function loadBaselines() {
|
|||||||
SSDTUpdatedProjectAfterSystemDbUpdateBaseline = await loadBaseline(baselineFolderPath, 'SSDTUpdatedProjectAfterSystemDbUpdateBaseline.xml');
|
SSDTUpdatedProjectAfterSystemDbUpdateBaseline = await loadBaseline(baselineFolderPath, 'SSDTUpdatedProjectAfterSystemDbUpdateBaseline.xml');
|
||||||
SSDTProjectBaselineWithCleanTarget = await loadBaseline(baselineFolderPath, 'SSDTProjectBaselineWithCleanTarget.xml');
|
SSDTProjectBaselineWithCleanTarget = await loadBaseline(baselineFolderPath, 'SSDTProjectBaselineWithCleanTarget.xml');
|
||||||
SSDTProjectBaselineWithCleanTargetAfterUpdate = await loadBaseline(baselineFolderPath, 'SSDTProjectBaselineWithCleanTargetAfterUpdate.xml');
|
SSDTProjectBaselineWithCleanTargetAfterUpdate = await loadBaseline(baselineFolderPath, 'SSDTProjectBaselineWithCleanTargetAfterUpdate.xml');
|
||||||
publishProfileBaseline = await loadBaseline(baselineFolderPath, 'publishProfileBaseline.publish.xml');
|
publishProfileIntegratedSecurityBaseline = await loadBaseline(baselineFolderPath, 'publishProfileIntegratedSecurityBaseline.publish.xml');
|
||||||
|
publishProfileSqlLoginBaseline = await loadBaseline(baselineFolderPath, 'publishProfileSqlLoginBaseline.publish.xml');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadBaseline(baselineFolderPath: string, fileName: string): Promise<string> {
|
async function loadBaseline(baselineFolderPath: string, fileName: string): Promise<string> {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<IncludeCompositeObjects>True</IncludeCompositeObjects>
|
<IncludeCompositeObjects>True</IncludeCompositeObjects>
|
||||||
<TargetDatabaseName>targetDb</TargetDatabaseName>
|
<TargetDatabaseName>targetDb</TargetDatabaseName>
|
||||||
<DeployScriptFileName>DatabaseProject1.sql</DeployScriptFileName>
|
<DeployScriptFileName>DatabaseProject1.sql</DeployScriptFileName>
|
||||||
|
<TargetConnectionString>Data Source=testserver;Integrated Security=true;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True</TargetConnectionString>
|
||||||
<ProfileVersionNumber>1</ProfileVersionNumber>
|
<ProfileVersionNumber>1</ProfileVersionNumber>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<IncludeCompositeObjects>True</IncludeCompositeObjects>
|
||||||
|
<TargetDatabaseName>targetDb</TargetDatabaseName>
|
||||||
|
<DeployScriptFileName>DatabaseProject1.sql</DeployScriptFileName>
|
||||||
|
<TargetConnectionString>Data Source=testserver;User Id=testUser;Password=abcd123;Integrated Security=false;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True</TargetConnectionString>
|
||||||
|
<ProfileVersionNumber>1</ProfileVersionNumber>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<SqlCmdVariable Include="ProdDatabaseName">
|
||||||
|
<DefaultValue>MyProdDatabase</DefaultValue>
|
||||||
|
<Value>$(SqlCmdVar__1)</Value>
|
||||||
|
</SqlCmdVariable>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -267,10 +267,12 @@ describe ('ProjectsController', function(): void {
|
|||||||
holler = publishHoller;
|
holler = publishHoller;
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
});
|
});
|
||||||
projController.setup(x => x.readPublishProfile(TypeMoq.It.isAny())).returns(() => {
|
projController.setup(x => x.readPublishProfileCallback(TypeMoq.It.isAny())).returns(() => {
|
||||||
holler = profileHoller;
|
holler = profileHoller;
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
databaseName: '',
|
databaseName: '',
|
||||||
|
connectionId: '',
|
||||||
|
connectionString: '',
|
||||||
sqlCmdVariables: {}
|
sqlCmdVariables: {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -291,21 +293,11 @@ describe ('ProjectsController', function(): void {
|
|||||||
should(holler).equal(generateHoller, 'executionCallback() is supposed to have been setup and called for GenerateScript scenario');
|
should(holler).equal(generateHoller, 'executionCallback() is supposed to have been setup and called for GenerateScript scenario');
|
||||||
|
|
||||||
dialog = await projController.object.publishProject(proj);
|
dialog = await projController.object.publishProject(proj);
|
||||||
await projController.object.readPublishProfile(vscode.Uri.parse('test'));
|
await projController.object.readPublishProfileCallback(vscode.Uri.parse('test'));
|
||||||
|
|
||||||
should(holler).equal(profileHoller, 'executionCallback() is supposed to have been setup and called for ReadPublishProfile scenario');
|
should(holler).equal(profileHoller, 'executionCallback() is supposed to have been setup and called for ReadPublishProfile scenario');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should read database name and SQLCMD variables from publish profile', async function (): Promise<void> {
|
|
||||||
await baselines.loadBaselines();
|
|
||||||
let profilePath = await testUtils.createTestFile(baselines.publishProfileBaseline, 'publishProfile.publish.xml');
|
|
||||||
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
|
|
||||||
|
|
||||||
let result = await projController.readPublishProfile(vscode.Uri.file(profilePath));
|
|
||||||
should(result.databaseName).equal('targetDb');
|
|
||||||
should(Object.keys(result.sqlCmdVariables).length).equal(1);
|
|
||||||
should(result.sqlCmdVariables['ProdDatabaseName']).equal('MyProdDatabase');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should copy dacpac to temp folder before publishing', async function (): Promise<void> {
|
it('Should copy dacpac to temp folder before publishing', async function (): Promise<void> {
|
||||||
const fakeDacpacContents = 'SwiftFlewHiawathasArrow';
|
const fakeDacpacContents = 'SwiftFlewHiawathasArrow';
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as sinon from 'sinon';
|
||||||
|
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';
|
||||||
|
|
||||||
|
|
||||||
|
describe('Publish profile tests', function (): void {
|
||||||
|
before(async function (): Promise<void> {
|
||||||
|
await baselines.loadBaselines();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function (): void {
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
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));
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
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: {}
|
||||||
|
};
|
||||||
|
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));
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw error when connecting does not work', async function (): Promise<void> {
|
||||||
|
await baselines.loadBaselines();
|
||||||
|
let profilePath = await testUtils.createTestFile(baselines.publishProfileIntegratedSecurityBaseline, 'publishProfile.publish.xml');
|
||||||
|
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
|
||||||
|
|
||||||
|
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').throws(new Error('Could not connect'));
|
||||||
|
sinon.stub(azdata.connection, 'getConnectionString').resolves(connectionString);
|
||||||
|
|
||||||
|
await testUtils.shouldThrowSpecificError(async () => await projController.readPublishProfileCallback(vscode.Uri.file(profilePath)), constants.unableToCreatePublishConnection('Could not connect'));
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user