mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
SQL Project: Opening connection viewlet after deploying database (#19544)
This commit is contained in:
@@ -47,6 +47,7 @@ import { ILocalDbDeployProfile, ISqlDbDeployProfile } from '../models/deploy/dep
|
|||||||
import { EntryType, FileProjectEntry, IDatabaseReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/projectEntry';
|
import { EntryType, FileProjectEntry, IDatabaseReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/projectEntry';
|
||||||
import { UpdateProjectAction, UpdateProjectDataModel } from '../models/api/updateProject';
|
import { UpdateProjectAction, UpdateProjectDataModel } from '../models/api/updateProject';
|
||||||
import { AzureSqlClient } from '../models/deploy/azureSqlClient';
|
import { AzureSqlClient } from '../models/deploy/azureSqlClient';
|
||||||
|
import { ConnectionService } from '../models/connections/connectionService';
|
||||||
|
|
||||||
const maxTableLength = 10;
|
const maxTableLength = 10;
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ export class ProjectsController {
|
|||||||
private buildInfo: DashboardData[] = [];
|
private buildInfo: DashboardData[] = [];
|
||||||
private publishInfo: PublishData[] = [];
|
private publishInfo: PublishData[] = [];
|
||||||
private deployService: DeployService;
|
private deployService: DeployService;
|
||||||
|
private connectionService: ConnectionService;
|
||||||
private azureSqlClient: AzureSqlClient;
|
private azureSqlClient: AzureSqlClient;
|
||||||
private autorestHelper: AutorestHelper;
|
private autorestHelper: AutorestHelper;
|
||||||
|
|
||||||
@@ -84,6 +86,7 @@ export class ProjectsController {
|
|||||||
this.buildHelper = new BuildHelper();
|
this.buildHelper = new BuildHelper();
|
||||||
this.azureSqlClient = new AzureSqlClient();
|
this.azureSqlClient = new AzureSqlClient();
|
||||||
this.deployService = new DeployService(this.azureSqlClient, this._outputChannel);
|
this.deployService = new DeployService(this.azureSqlClient, this._outputChannel);
|
||||||
|
this.connectionService = new ConnectionService(this._outputChannel);
|
||||||
this.autorestHelper = new AutorestHelper(this._outputChannel);
|
this.autorestHelper = new AutorestHelper(this._outputChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +296,7 @@ export class ProjectsController {
|
|||||||
if (deployProfile.sqlDbSetting) {
|
if (deployProfile.sqlDbSetting) {
|
||||||
|
|
||||||
// Connecting to the deployed db to add the profile to connection viewlet
|
// Connecting to the deployed db to add the profile to connection viewlet
|
||||||
await this.deployService.getConnection(deployProfile.sqlDbSetting, true, deployProfile.sqlDbSetting.dbName);
|
await this.connectionService.getConnection(deployProfile.sqlDbSetting, true, deployProfile.sqlDbSetting.dbName);
|
||||||
}
|
}
|
||||||
void vscode.window.showInformationMessage(constants.publishProjectSucceed);
|
void vscode.window.showInformationMessage(constants.publishProjectSucceed);
|
||||||
} else {
|
} else {
|
||||||
@@ -335,7 +338,7 @@ export class ProjectsController {
|
|||||||
const publishResult = await this.publishOrScriptProject(project, deployProfile.deploySettings, true);
|
const publishResult = await this.publishOrScriptProject(project, deployProfile.deploySettings, true);
|
||||||
if (publishResult && publishResult.success) {
|
if (publishResult && publishResult.success) {
|
||||||
if (deployProfile.localDbSetting) {
|
if (deployProfile.localDbSetting) {
|
||||||
await this.deployService.getConnection(deployProfile.localDbSetting, true, deployProfile.localDbSetting.dbName);
|
await this.connectionService.getConnection(deployProfile.localDbSetting, true, deployProfile.localDbSetting.dbName);
|
||||||
}
|
}
|
||||||
void vscode.window.showInformationMessage(constants.publishProjectSucceed);
|
void vscode.window.showInformationMessage(constants.publishProjectSucceed);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as constants from '../../common/constants';
|
||||||
|
import * as utils from '../../common/utils';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { ConnectionResult } from 'azdata';
|
||||||
|
import { IFireWallRuleError } from 'vscode-mssql';
|
||||||
|
|
||||||
|
export interface ISqlConnectionProperties {
|
||||||
|
tenantId?: string,
|
||||||
|
accountId?: string
|
||||||
|
serverName: string,
|
||||||
|
userName: string,
|
||||||
|
password: string,
|
||||||
|
port: number,
|
||||||
|
dbName: string,
|
||||||
|
profileName?: string,
|
||||||
|
connectionRetryTimeout?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Includes methods to open connections and interact with connection views
|
||||||
|
*/
|
||||||
|
export class ConnectionService {
|
||||||
|
|
||||||
|
constructor(private _outputChannel: vscode.OutputChannel) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private defaultSqlRetryTimeoutInSec: number = 10;
|
||||||
|
private defaultSqlNumberOfRetries: number = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to a database
|
||||||
|
* @param profile connection profile
|
||||||
|
* @param saveConnectionAndPassword if true, connection will be saved in the connection view
|
||||||
|
* @param database database name
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
private async connectToDatabase(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<ConnectionResult | string | undefined> {
|
||||||
|
const azdataApi = utils.getAzdataApi();
|
||||||
|
const vscodeMssqlApi = azdataApi ? undefined : await utils.getVscodeMssqlApi();
|
||||||
|
if (azdataApi) {
|
||||||
|
const connectionProfile = {
|
||||||
|
password: profile.password,
|
||||||
|
serverName: `${profile.serverName},${profile.port}`,
|
||||||
|
database: database,
|
||||||
|
savePassword: saveConnectionAndPassword,
|
||||||
|
userName: profile.userName,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
saveProfile: false,
|
||||||
|
id: '',
|
||||||
|
connectionName: profile.profileName,
|
||||||
|
options: [],
|
||||||
|
authenticationType: 'SqlLogin'
|
||||||
|
};
|
||||||
|
return await azdataApi.connection.connect(connectionProfile, saveConnectionAndPassword, false);
|
||||||
|
} else if (vscodeMssqlApi) {
|
||||||
|
const connectionProfile = {
|
||||||
|
password: profile.password,
|
||||||
|
server: `${profile.serverName}`,
|
||||||
|
port: profile.port,
|
||||||
|
database: database,
|
||||||
|
savePassword: saveConnectionAndPassword,
|
||||||
|
user: profile.userName,
|
||||||
|
authenticationType: 'SqlLogin',
|
||||||
|
encrypt: false,
|
||||||
|
connectTimeout: 30,
|
||||||
|
applicationName: 'SQL Database Project',
|
||||||
|
accountId: profile.accountId,
|
||||||
|
azureAccountToken: undefined,
|
||||||
|
applicationIntent: undefined,
|
||||||
|
attachDbFilename: undefined,
|
||||||
|
connectRetryCount: undefined,
|
||||||
|
connectRetryInterval: undefined,
|
||||||
|
connectionString: undefined,
|
||||||
|
currentLanguage: undefined,
|
||||||
|
email: undefined,
|
||||||
|
failoverPartner: undefined,
|
||||||
|
loadBalanceTimeout: undefined,
|
||||||
|
maxPoolSize: undefined,
|
||||||
|
minPoolSize: undefined,
|
||||||
|
multiSubnetFailover: undefined,
|
||||||
|
multipleActiveResultSets: undefined,
|
||||||
|
packetSize: undefined,
|
||||||
|
persistSecurityInfo: undefined,
|
||||||
|
pooling: undefined,
|
||||||
|
replication: undefined,
|
||||||
|
trustServerCertificate: undefined,
|
||||||
|
typeSystemVersion: undefined,
|
||||||
|
workstationId: undefined,
|
||||||
|
profileName: profile.profileName,
|
||||||
|
expiresOn: undefined,
|
||||||
|
tenantId: profile.tenantId
|
||||||
|
};
|
||||||
|
let connectionUrl = '';
|
||||||
|
try {
|
||||||
|
connectionUrl = await vscodeMssqlApi.connect(connectionProfile, saveConnectionAndPassword);
|
||||||
|
} catch (err) {
|
||||||
|
const firewallRuleError = <IFireWallRuleError>err;
|
||||||
|
if (firewallRuleError?.connectionUri) {
|
||||||
|
await vscodeMssqlApi.promptForFirewallRule(err.connectionUri, connectionProfile);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If connected successfully and saved the connection in the view, open the connection view
|
||||||
|
if (saveConnectionAndPassword && connectionUrl) {
|
||||||
|
await vscode.commands.executeCommand('objectExplorer.focus');
|
||||||
|
}
|
||||||
|
return connectionUrl;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the connection result. If using azdata API, verifies connection was successful and connection id is returns
|
||||||
|
* If using vscode API, verifies the connection url is returns
|
||||||
|
* @param connection connection result or connection Id
|
||||||
|
* @returns validation result
|
||||||
|
*/
|
||||||
|
private async validateConnection(connection: ConnectionResult | string | undefined): Promise<utils.ValidationResult> {
|
||||||
|
const azdataApi = utils.getAzdataApi();
|
||||||
|
if (!connection) {
|
||||||
|
return { validated: false, errorMessage: constants.connectionFailedError('No result returned') };
|
||||||
|
} else if (azdataApi) {
|
||||||
|
const connectionResult = <ConnectionResult>connection;
|
||||||
|
if (connectionResult) {
|
||||||
|
const connected = connectionResult !== undefined && connectionResult.connected && connectionResult.connectionId !== undefined;
|
||||||
|
return { validated: connected, errorMessage: connected ? '' : constants.connectionFailedError(connectionResult?.errorMessage!) };
|
||||||
|
} else {
|
||||||
|
return { validated: false, errorMessage: constants.connectionFailedError('') };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { validated: connection !== undefined, errorMessage: constants.connectionFailedError('') };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats connection result to string to be able to add to log
|
||||||
|
* @param connection connection result or connection Id
|
||||||
|
* @returns formatted connection result
|
||||||
|
*/
|
||||||
|
private async formatConnectionResult(connection: ConnectionResult | string | undefined): Promise<string> {
|
||||||
|
const azdataApi = utils.getAzdataApi();
|
||||||
|
const connectionResult = connection !== undefined && azdataApi ? <ConnectionResult>connection : undefined;
|
||||||
|
return connectionResult?.connected ? connectionResult.connectionId! : <string>connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a connections and returns the connection id
|
||||||
|
* @param profile connection profile
|
||||||
|
* @param saveConnectionAndPassword is set to true the connection will be saved in the connection view
|
||||||
|
* @param database database name
|
||||||
|
* @returns connection id
|
||||||
|
*/
|
||||||
|
public async getConnection(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<string | undefined> {
|
||||||
|
const azdataApi = utils.getAzdataApi();
|
||||||
|
let connection = await utils.retry(
|
||||||
|
constants.connectingToSqlServerMessage,
|
||||||
|
async () => {
|
||||||
|
return await this.connectToDatabase(profile, saveConnectionAndPassword, database);
|
||||||
|
},
|
||||||
|
this.validateConnection,
|
||||||
|
this.formatConnectionResult,
|
||||||
|
this._outputChannel,
|
||||||
|
this.defaultSqlNumberOfRetries, profile.connectionRetryTimeout || this.defaultSqlRetryTimeoutInSec);
|
||||||
|
|
||||||
|
if (connection) {
|
||||||
|
const connectionResult = <ConnectionResult>connection;
|
||||||
|
if (azdataApi) {
|
||||||
|
utils.throwIfNotConnected(connectionResult);
|
||||||
|
return azdataApi.connection.getUriForConnection(connectionResult.connectionId!);
|
||||||
|
} else {
|
||||||
|
return <string>connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
import { IDeploySettings } from '../IDeploySettings';
|
import { IDeploySettings } from '../IDeploySettings';
|
||||||
import type * as azdataType from 'azdata';
|
import type * as azdataType from 'azdata';
|
||||||
import { IAzureAccountSession } from 'vscode-mssql';
|
import { IAzureAccountSession } from 'vscode-mssql';
|
||||||
|
import { ISqlConnectionProperties } from '../connections/connectionService';
|
||||||
|
|
||||||
export enum AppSettingType {
|
export enum AppSettingType {
|
||||||
None,
|
None,
|
||||||
@@ -38,17 +39,7 @@ export interface ILocalDbSetting extends ISqlConnectionProperties {
|
|||||||
dockerBaseImageEula: string,
|
dockerBaseImageEula: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISqlConnectionProperties {
|
|
||||||
tenantId?: string,
|
|
||||||
accountId?: string
|
|
||||||
serverName: string,
|
|
||||||
userName: string,
|
|
||||||
password: string,
|
|
||||||
port: number,
|
|
||||||
dbName: string,
|
|
||||||
profileName?: string,
|
|
||||||
connectionRetryTimeout?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DockerImageInfo {
|
export interface DockerImageInfo {
|
||||||
name: string,
|
name: string,
|
||||||
|
|||||||
@@ -3,18 +3,15 @@
|
|||||||
* 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 { AppSettingType, IDeployAppIntegrationProfile, ILocalDbDeployProfile, ILocalDbSetting, ISqlConnectionProperties, ISqlDbDeployProfile } from './deployProfile';
|
import { ILocalDbDeployProfile, ILocalDbSetting, ISqlDbDeployProfile } from './deployProfile';
|
||||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||||
import { Project } from '../project';
|
import { Project } from '../project';
|
||||||
import * as constants from '../../common/constants';
|
import * as constants from '../../common/constants';
|
||||||
import * as utils from '../../common/utils';
|
import * as utils from '../../common/utils';
|
||||||
import * as fse from 'fs-extra';
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { ConnectionResult } from 'azdata';
|
|
||||||
import * as templates from '../../templates/templates';
|
|
||||||
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
|
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
|
||||||
import { AzureSqlClient } from './azureSqlClient';
|
import { AzureSqlClient } from './azureSqlClient';
|
||||||
import { IFireWallRuleError } from 'vscode-mssql';
|
import { ConnectionService } from '../connections/connectionService';
|
||||||
|
|
||||||
interface DockerImageSpec {
|
interface DockerImageSpec {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -25,73 +22,11 @@ export class DeployService {
|
|||||||
|
|
||||||
constructor(private _azureSqlClient = new AzureSqlClient(), private _outputChannel: vscode.OutputChannel, shellExecutionHelper: ShellExecutionHelper | undefined = undefined) {
|
constructor(private _azureSqlClient = new AzureSqlClient(), private _outputChannel: vscode.OutputChannel, shellExecutionHelper: ShellExecutionHelper | undefined = undefined) {
|
||||||
this._shellExecutionHelper = shellExecutionHelper ?? new ShellExecutionHelper(this._outputChannel);
|
this._shellExecutionHelper = shellExecutionHelper ?? new ShellExecutionHelper(this._outputChannel);
|
||||||
|
this._connectionService = new ConnectionService(this._outputChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _shellExecutionHelper: ShellExecutionHelper;
|
private _shellExecutionHelper: ShellExecutionHelper;
|
||||||
private DefaultSqlRetryTimeoutInSec: number = 10;
|
private _connectionService: ConnectionService;
|
||||||
private DefaultSqlNumberOfRetries: number = 3;
|
|
||||||
|
|
||||||
private createConnectionStringTemplate(runtime: string | undefined): string {
|
|
||||||
switch (runtime?.toLocaleLowerCase()) {
|
|
||||||
case 'dotnet':
|
|
||||||
return constants.defaultConnectionStringTemplate;
|
|
||||||
break;
|
|
||||||
// TODO: add connection strings for other languages
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private findAppRuntime(profile: IDeployAppIntegrationProfile, appSettingContent: any): string | undefined {
|
|
||||||
switch (profile.appSettingType) {
|
|
||||||
case AppSettingType.AzureFunction:
|
|
||||||
return <string>appSettingContent?.Values['FUNCTIONS_WORKER_RUNTIME'];
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async updateAppSettings(profile: IDeployAppIntegrationProfile, deployProfile: ILocalDbDeployProfile | undefined): Promise<void> {
|
|
||||||
// Update app settings
|
|
||||||
//
|
|
||||||
if (!profile.appSettingFile) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.logToOutput(constants.deployAppSettingUpdating(profile.appSettingFile));
|
|
||||||
|
|
||||||
// TODO: handle parsing errors
|
|
||||||
let content = JSON.parse(fse.readFileSync(profile.appSettingFile, 'utf8'));
|
|
||||||
if (content && content.Values) {
|
|
||||||
let connectionString: string | undefined = '';
|
|
||||||
if (deployProfile && deployProfile.localDbSetting) {
|
|
||||||
// Find the runtime and generate the connection string for the runtime
|
|
||||||
//
|
|
||||||
const runtime = this.findAppRuntime(profile, content);
|
|
||||||
let connectionStringTemplate = this.createConnectionStringTemplate(runtime);
|
|
||||||
const macroDict: Record<string, string> = {
|
|
||||||
'SERVER': deployProfile?.localDbSetting?.serverName || '',
|
|
||||||
'PORT': deployProfile?.localDbSetting?.port?.toString() || '',
|
|
||||||
'USER': deployProfile?.localDbSetting?.userName || '',
|
|
||||||
'SA_PASSWORD': deployProfile?.localDbSetting?.password || '',
|
|
||||||
'DATABASE': deployProfile?.localDbSetting?.dbName || '',
|
|
||||||
};
|
|
||||||
|
|
||||||
connectionString = templates.macroExpansion(connectionStringTemplate, macroDict);
|
|
||||||
} else if (deployProfile?.deploySettings?.connectionUri) {
|
|
||||||
connectionString = await this.getConnectionString(deployProfile?.deploySettings?.connectionUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectionString && profile.envVariableName) {
|
|
||||||
content.Values[profile.envVariableName] = connectionString;
|
|
||||||
fse.writeFileSync(profile.appSettingFile, JSON.stringify(content, undefined, 4));
|
|
||||||
this.logToOutput(`app setting '${profile.appSettingFile}' has been updated. env variable name: ${profile.envVariableName} connection String: ${connectionString}`);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.logToOutput(constants.deployAppSettingUpdateFailed(profile.appSettingFile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async verifyDocker(): Promise<void> {
|
private async verifyDocker(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -149,7 +84,7 @@ export class DeployService {
|
|||||||
this.logToOutput(constants.azureSqlServerCreated(profile?.sqlDbSetting?.serverName));
|
this.logToOutput(constants.azureSqlServerCreated(profile?.sqlDbSetting?.serverName));
|
||||||
|
|
||||||
// Connect to the server
|
// Connect to the server
|
||||||
return await this.getConnection(profile.sqlDbSetting, false, constants.master);
|
return await this._connectionService.getConnection(profile.sqlDbSetting, false, constants.master);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -209,7 +144,7 @@ export class DeployService {
|
|||||||
|
|
||||||
if (runningDockerId) {
|
if (runningDockerId) {
|
||||||
this.logToOutput(constants.dockerContainerCreatedMessage(runningDockerId));
|
this.logToOutput(constants.dockerContainerCreatedMessage(runningDockerId));
|
||||||
return await this.getConnection(profile.localDbSetting, false, 'master');
|
return await this._connectionService.getConnection(profile.localDbSetting, false, 'master');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.logToOutput(constants.dockerContainerFailedToRunErrorMessage);
|
this.logToOutput(constants.dockerContainerFailedToRunErrorMessage);
|
||||||
@@ -235,145 +170,6 @@ export class DeployService {
|
|||||||
return await this.executeCommand(`docker ps -q -a --filter label=${dockerImageSpec.label} -q`);
|
return await this.executeCommand(`docker ps -q -a --filter label=${dockerImageSpec.label} -q`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getConnectionString(connectionUri: string): Promise<string | undefined> {
|
|
||||||
const azdataApi = utils.getAzdataApi();
|
|
||||||
if (azdataApi) {
|
|
||||||
const connection = await azdataApi.connection.getConnection(connectionUri);
|
|
||||||
if (connection) {
|
|
||||||
return await azdataApi.connection.getConnectionString(connection.connectionId, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: vscode connections string
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connects to a database
|
|
||||||
private async connectToDatabase(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<ConnectionResult | string | undefined> {
|
|
||||||
const azdataApi = utils.getAzdataApi();
|
|
||||||
const vscodeMssqlApi = azdataApi ? undefined : await utils.getVscodeMssqlApi();
|
|
||||||
if (azdataApi) {
|
|
||||||
const connectionProfile = {
|
|
||||||
password: profile.password,
|
|
||||||
serverName: `${profile.serverName},${profile.port}`,
|
|
||||||
database: database,
|
|
||||||
savePassword: saveConnectionAndPassword,
|
|
||||||
userName: profile.userName,
|
|
||||||
providerName: 'MSSQL',
|
|
||||||
saveProfile: false,
|
|
||||||
id: '',
|
|
||||||
connectionName: profile.profileName,
|
|
||||||
options: [],
|
|
||||||
authenticationType: 'SqlLogin'
|
|
||||||
};
|
|
||||||
return await azdataApi.connection.connect(connectionProfile, saveConnectionAndPassword, false);
|
|
||||||
} else if (vscodeMssqlApi) {
|
|
||||||
const connectionProfile = {
|
|
||||||
password: profile.password,
|
|
||||||
server: `${profile.serverName}`,
|
|
||||||
port: profile.port,
|
|
||||||
database: database,
|
|
||||||
savePassword: saveConnectionAndPassword,
|
|
||||||
user: profile.userName,
|
|
||||||
authenticationType: 'SqlLogin',
|
|
||||||
encrypt: false,
|
|
||||||
connectTimeout: 30,
|
|
||||||
applicationName: 'SQL Database Project',
|
|
||||||
accountId: profile.accountId,
|
|
||||||
azureAccountToken: undefined,
|
|
||||||
applicationIntent: undefined,
|
|
||||||
attachDbFilename: undefined,
|
|
||||||
connectRetryCount: undefined,
|
|
||||||
connectRetryInterval: undefined,
|
|
||||||
connectionString: undefined,
|
|
||||||
currentLanguage: undefined,
|
|
||||||
email: undefined,
|
|
||||||
failoverPartner: undefined,
|
|
||||||
loadBalanceTimeout: undefined,
|
|
||||||
maxPoolSize: undefined,
|
|
||||||
minPoolSize: undefined,
|
|
||||||
multiSubnetFailover: undefined,
|
|
||||||
multipleActiveResultSets: undefined,
|
|
||||||
packetSize: undefined,
|
|
||||||
persistSecurityInfo: undefined,
|
|
||||||
pooling: undefined,
|
|
||||||
replication: undefined,
|
|
||||||
trustServerCertificate: undefined,
|
|
||||||
typeSystemVersion: undefined,
|
|
||||||
workstationId: undefined,
|
|
||||||
profileName: profile.profileName,
|
|
||||||
expiresOn: undefined,
|
|
||||||
tenantId: profile.tenantId
|
|
||||||
};
|
|
||||||
let connectionUrl = '';
|
|
||||||
try {
|
|
||||||
connectionUrl = await vscodeMssqlApi.connect(connectionProfile, saveConnectionAndPassword);
|
|
||||||
} catch (err) {
|
|
||||||
const firewallRuleError = <IFireWallRuleError>err;
|
|
||||||
if (firewallRuleError?.connectionUri) {
|
|
||||||
await vscodeMssqlApi.promptForFirewallRule(err.connectionUri, connectionProfile);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return connectionUrl;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validates the connection result. If using azdata API, verifies connection was successful and connection id is returns
|
|
||||||
// If using vscode API, verifies the connection url is returns
|
|
||||||
private async validateConnection(connection: ConnectionResult | string | undefined): Promise<utils.ValidationResult> {
|
|
||||||
const azdataApi = utils.getAzdataApi();
|
|
||||||
if (!connection) {
|
|
||||||
return { validated: false, errorMessage: constants.connectionFailedError('No result returned') };
|
|
||||||
} else if (azdataApi) {
|
|
||||||
const connectionResult = <ConnectionResult>connection;
|
|
||||||
if (connectionResult) {
|
|
||||||
const connected = connectionResult !== undefined && connectionResult.connected && connectionResult.connectionId !== undefined;
|
|
||||||
return { validated: connected, errorMessage: connected ? '' : constants.connectionFailedError(connectionResult?.errorMessage!) };
|
|
||||||
} else {
|
|
||||||
return { validated: false, errorMessage: constants.connectionFailedError('') };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return { validated: connection !== undefined, errorMessage: constants.connectionFailedError('') };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Formats connection result to string to be able to add to log
|
|
||||||
private async formatConnectionResult(connection: ConnectionResult | string | undefined): Promise<string> {
|
|
||||||
const azdataApi = utils.getAzdataApi();
|
|
||||||
const connectionResult = connection !== undefined && azdataApi ? <ConnectionResult>connection : undefined;
|
|
||||||
return connectionResult?.connected ? connectionResult.connectionId! : <string>connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getConnection(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<string | undefined> {
|
|
||||||
const azdataApi = utils.getAzdataApi();
|
|
||||||
let connection = await utils.retry(
|
|
||||||
constants.connectingToSqlServerMessage,
|
|
||||||
async () => {
|
|
||||||
return await this.connectToDatabase(profile, saveConnectionAndPassword, database);
|
|
||||||
},
|
|
||||||
this.validateConnection,
|
|
||||||
this.formatConnectionResult,
|
|
||||||
this._outputChannel,
|
|
||||||
this.DefaultSqlNumberOfRetries, profile.connectionRetryTimeout || this.DefaultSqlRetryTimeoutInSec);
|
|
||||||
|
|
||||||
if (connection) {
|
|
||||||
const connectionResult = <ConnectionResult>connection;
|
|
||||||
if (azdataApi) {
|
|
||||||
utils.throwIfNotConnected(connectionResult);
|
|
||||||
return azdataApi.connection.getUriForConnection(connectionResult.connectionId!);
|
|
||||||
} else {
|
|
||||||
return <string>connection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeTask<T>(taskName: string, task: () => Promise<T>): Promise<T> {
|
private async executeTask<T>(taskName: string, task: () => Promise<T>): Promise<T> {
|
||||||
const azdataApi = utils.getAzdataApi();
|
const azdataApi = utils.getAzdataApi();
|
||||||
if (azdataApi) {
|
if (azdataApi) {
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ import { DeployService } from '../../models/deploy/deployService';
|
|||||||
import { Project } from '../../models/project';
|
import { Project } from '../../models/project';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { AppSettingType, ILocalDbDeployProfile, ISqlDbDeployProfile } from '../../models/deploy/deployProfile';
|
import { ILocalDbDeployProfile, ISqlDbDeployProfile } from '../../models/deploy/deployProfile';
|
||||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||||
import * as fse from 'fs-extra';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as constants from '../../common/constants';
|
import * as constants from '../../common/constants';
|
||||||
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
|
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import { AzureSqlClient } from '../../models/deploy/azureSqlClient';
|
import { AzureSqlClient } from '../../models/deploy/azureSqlClient';
|
||||||
|
import { ConnectionService } from '../../models/connections/connectionService';
|
||||||
|
|
||||||
export interface TestContext {
|
export interface TestContext {
|
||||||
outputChannel: vscode.OutputChannel;
|
outputChannel: vscode.OutputChannel;
|
||||||
@@ -140,125 +139,17 @@ describe('deploy service', function (): void {
|
|||||||
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
|
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
|
||||||
shellExecutionHelper.setup(x => x.runStreamedCommand(TypeMoq.It.isAny(),
|
shellExecutionHelper.setup(x => x.runStreamedCommand(TypeMoq.It.isAny(),
|
||||||
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
|
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
|
||||||
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
|
const connectionService = new ConnectionService(testContext.outputChannel);
|
||||||
let connectionStub = sandbox.stub(azdata.connection, 'connect');
|
let connectionStub = sandbox.stub(azdata.connection, 'connect');
|
||||||
connectionStub.onFirstCall().returns(Promise.resolve(mockFailedConnectionResult));
|
connectionStub.onFirstCall().returns(Promise.resolve(mockFailedConnectionResult));
|
||||||
connectionStub.onSecondCall().returns(Promise.resolve(mockConnectionResult));
|
connectionStub.onSecondCall().returns(Promise.resolve(mockConnectionResult));
|
||||||
sandbox.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve('connection'));
|
sandbox.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve('connection'));
|
||||||
sandbox.stub(azdata.tasks, 'startBackgroundOperation').callThrough();
|
sandbox.stub(azdata.tasks, 'startBackgroundOperation').callThrough();
|
||||||
|
|
||||||
let connection = await deployService.getConnection(localDbSettings, false, 'master');
|
let connection = await connectionService.getConnection(localDbSettings, false, 'master');
|
||||||
should(connection).equals('connection');
|
should(connection).equals('connection');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should update app settings successfully', async function (): Promise<void> {
|
|
||||||
const testContext = createContext();
|
|
||||||
const projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
|
|
||||||
const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath);
|
|
||||||
const jsondData =
|
|
||||||
{
|
|
||||||
IsEncrypted: false,
|
|
||||||
Values: {
|
|
||||||
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
|
|
||||||
FUNCTIONS_WORKER_RUNTIME: 'dotnet'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let settingContent = JSON.stringify(jsondData, undefined, 4);
|
|
||||||
const expected =
|
|
||||||
{
|
|
||||||
IsEncrypted: false,
|
|
||||||
Values: {
|
|
||||||
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
|
|
||||||
FUNCTIONS_WORKER_RUNTIME: 'dotnet',
|
|
||||||
SQLConnectionString: 'Data Source=localhost,1433;Initial Catalog=test;User id=sa;Password=PLACEHOLDER;'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const filePath = path.join(project1.projectFolderPath, 'local.settings.json');
|
|
||||||
await fse.writeFile(filePath, settingContent);
|
|
||||||
|
|
||||||
const deployProfile: ILocalDbDeployProfile = {
|
|
||||||
localDbSetting: {
|
|
||||||
dbName: 'test',
|
|
||||||
password: 'PLACEHOLDER',
|
|
||||||
port: 1433,
|
|
||||||
serverName: 'localhost',
|
|
||||||
userName: 'sa',
|
|
||||||
dockerBaseImage: 'image',
|
|
||||||
dockerBaseImageEula: ''
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const appInteg = {
|
|
||||||
appSettingType: AppSettingType.AzureFunction,
|
|
||||||
appSettingFile: filePath,
|
|
||||||
deploySettings: undefined,
|
|
||||||
envVariableName: 'SQLConnectionString'
|
|
||||||
};
|
|
||||||
|
|
||||||
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
|
|
||||||
shellExecutionHelper.setup(x => x.runStreamedCommand(TypeMoq.It.isAny(),
|
|
||||||
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
|
|
||||||
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
|
|
||||||
|
|
||||||
await deployService.updateAppSettings(appInteg, deployProfile);
|
|
||||||
let newContent = JSON.parse(fse.readFileSync(filePath, 'utf8'));
|
|
||||||
should(newContent).deepEqual(expected);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should update app settings using connection uri if there are no local settings', async function (): Promise<void> {
|
|
||||||
const testContext = createContext();
|
|
||||||
const projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
|
|
||||||
const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath);
|
|
||||||
const jsondData =
|
|
||||||
{
|
|
||||||
IsEncrypted: false,
|
|
||||||
Values: {
|
|
||||||
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
|
|
||||||
FUNCTIONS_WORKER_RUNTIME: 'dotnet'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let settingContent = JSON.stringify(jsondData, undefined, 4);
|
|
||||||
const expected =
|
|
||||||
{
|
|
||||||
IsEncrypted: false,
|
|
||||||
Values: {
|
|
||||||
AzureWebJobsStorage: 'UseDevelopmentStorage=true',
|
|
||||||
FUNCTIONS_WORKER_RUNTIME: 'dotnet',
|
|
||||||
SQLConnectionString: 'connectionString'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const filePath = path.join(project1.projectFolderPath, 'local.settings.json');
|
|
||||||
await fse.writeFile(filePath, settingContent);
|
|
||||||
|
|
||||||
const deployProfile: ILocalDbDeployProfile = {
|
|
||||||
|
|
||||||
deploySettings: {
|
|
||||||
connectionUri: 'connection',
|
|
||||||
databaseName: 'test',
|
|
||||||
serverName: 'test'
|
|
||||||
},
|
|
||||||
localDbSetting: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
const appInteg = {
|
|
||||||
appSettingType: AppSettingType.AzureFunction,
|
|
||||||
appSettingFile: filePath,
|
|
||||||
envVariableName: 'SQLConnectionString',
|
|
||||||
};
|
|
||||||
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
|
|
||||||
shellExecutionHelper.setup(x => x.runStreamedCommand(TypeMoq.It.isAny(),
|
|
||||||
undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('id'));
|
|
||||||
const deployService = new DeployService(testContext.azureSqlClient.object, testContext.outputChannel, shellExecutionHelper.object);
|
|
||||||
let connection = new azdata.connection.ConnectionProfile();
|
|
||||||
sandbox.stub(azdata.connection, 'getConnection').returns(Promise.resolve(connection));
|
|
||||||
|
|
||||||
sandbox.stub(azdata.connection, 'getConnectionString').returns(Promise.resolve('connectionString'));
|
|
||||||
await deployService.updateAppSettings(appInteg, deployProfile);
|
|
||||||
let newContent = JSON.parse(fse.readFileSync(filePath, 'utf8'));
|
|
||||||
should(newContent).deepEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should clean a list of docker images successfully', async function (): Promise<void> {
|
it('Should clean a list of docker images successfully', async function (): Promise<void> {
|
||||||
const testContext = createContext();
|
const testContext = createContext();
|
||||||
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
|
const shellExecutionHelper = TypeMoq.Mock.ofType(ShellExecutionHelper);
|
||||||
|
|||||||
Reference in New Issue
Block a user