mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 09:35:36 -05:00
SQL Database Project - Deploy db to docker (#16406)
Added a new command to deploy the project to docker
This commit is contained in:
@@ -126,6 +126,53 @@ export const selectDatabase = localize('selectDatabase', "Select database");
|
||||
export const done = localize('done', "Done");
|
||||
export const nameMustNotBeEmpty = localize('nameMustNotBeEmpty', "Name must not be empty");
|
||||
|
||||
// Deploy
|
||||
export const selectDeployOption = localize('selectDeployOption', "Select where to deploy the project to");
|
||||
export const deployToExistingServer = localize('deployToExistingServer', "Deploy to existing server");
|
||||
export const deployToDockerContainer = localize('deployToDockerContainer', "Deploy to docker container");
|
||||
export const enterPortNumber = localize('enterPortNumber', "Enter port number or press enter to use the default value");
|
||||
export const enterConnectionStringEnvName = localize('enterConnectionStringEnvName', "Enter connection string environment variable name");
|
||||
export const enterConnectionStringTemplate = localize('enterConnectionStringTemplate', "Enter connection string template");
|
||||
export const enterPassword = localize('enterPassword', "Enter password or press enter to use the generated password");
|
||||
export const portMustBeNumber = localize('portMustNotBeNumber', "Port must a be number");
|
||||
export const valueCannotBeEmpty = localize('valueCannotBeEmpty', "Value cannot be empty");
|
||||
export const dockerImageLabelPrefix = 'source=sqldbproject';
|
||||
export const dockerImageNamePrefix = 'sqldbproject';
|
||||
export const connectionNamePrefix = 'SQLDbProject';
|
||||
export const dockerBaseImage = 'mcr.microsoft.com/azure-sql-edge:latest';
|
||||
export const commandsFolderName = 'commands';
|
||||
export const mssqlFolderName = '.mssql';
|
||||
export const dockerFileName = 'Dockerfile';
|
||||
export const startCommandName = 'start.sh';
|
||||
export const defaultPortNumber = '1433';
|
||||
export const defaultConnectionStringEnvVarName = 'SQLConnectionString';
|
||||
export const defaultConnectionStringTemplate = 'Data Source=@@SERVER@@,@@PORT@@;Initial Catalog=@@DATABASE@@;User id=@@USER@@;Password=@@SA_PASSWORD@@;';
|
||||
export const azureFunctionLocalSettingsFileName = 'local.settings.json';
|
||||
export const enterConnStringTemplateDescription = localize('enterConnStringTemplateDescription', "Enter a template for SQL connection string");
|
||||
export const appSettingPrompt = localize('appSettingPrompt', "Would you like to update Azure Function local.settings.json with the new connection string?");
|
||||
export const enterConnectionStringEnvNameDescription = localize('enterConnectionStringEnvNameDescription', "Enter environment variable for SQL connection string");
|
||||
export const deployDbTaskName = localize('deployDbTaskName', "Deploying SQL Db Project Locally");
|
||||
export const deployProjectSucceed = localize('deployProjectSucceed', "Database project deployed successfully");
|
||||
export const cleaningDockerImagesMessage = localize('cleaningDockerImagesMessage', "Cleaning existing deployments...");
|
||||
export const creatingDeploymentSettingsMessage = localize('creatingDeploymentSettingsMessage', "Creating deployment settings ...");
|
||||
export const runningDockerMessage = localize('runningDockerMessage', "Building and running the docker container ...");
|
||||
export const dockerContainerNotRunningErrorMessage = localize('dockerContainerNotRunningErrorMessage', "Docker container is not running");
|
||||
export const dockerContainerFailedToRunErrorMessage = localize('dockerContainerFailedToRunErrorMessage', "Failed to run the docker container");
|
||||
export const connectingToSqlServerOnDockerMessage = localize('connectingToSqlServerOnDockerMessage', "Connecting to SQL Server on Docker");
|
||||
export const deployProjectFailedMessage = localize('deployProjectFailedMessage', "Failed to open a connection to the deployed database'");
|
||||
export function taskFailedError(taskName: string, err: string): string { return localize('taskFailedError.error', "Failed to complete task '{0}'. Error: {1}", taskName, err); }
|
||||
export function deployProjectFailed(errorMessage: string) { return localize('deployProjectFailed', "Failed to deploy project. Check output pane for more details. {0}", errorMessage); }
|
||||
export function deployAppSettingUpdateFailed(appSetting: string) { return localize('deployAppSettingUpdateFailed', "Failed to update app setting '{0}'", appSetting); }
|
||||
export function deployAppSettingUpdating(appSetting: string) { return localize('deployAppSettingUpdating', "Updating app setting: '{0}'", appSetting); }
|
||||
export function connectionFailedError(error: string) { return localize('connectionFailedError', "Connection failed error: '{0}'", error); }
|
||||
export function dockerContainerCreatedMessage(id: string) { return localize('dockerContainerCreatedMessage', "Docker created id: '{0}'", id); }
|
||||
export function dockerLogMessage(log: string) { return localize('dockerLogMessage', "Docker logs: '{0}'", log); }
|
||||
export function retryWaitMessage(numberOfSeconds: number, name: string) { return localize('retryWaitMessage', "Waiting for {0} seconds before another attempt for operation '{1}'", numberOfSeconds, name); }
|
||||
export function retryRunMessage(attemptNumber: number, numberOfAttempts: number, name: string) { return localize('retryRunMessage', "Running operation '{2}' Attempt {0} of {1}", attemptNumber, numberOfAttempts, name); }
|
||||
export function retrySucceedMessage(name: string, result: string) { return localize('retrySucceedMessage', "Operation '{0}' completed successfully. Result: {1}", name, result); }
|
||||
export function retryFailedMessage(name: string, result: string, error: string) { return localize('retryFailedMessage', "Operation '{0}' failed. Re-trying... Current Result: {1}. Error: '{2}'", name, result, error); }
|
||||
export function retryMessage(name: string, error: string) { return localize('retryMessage', "Operation '{0}' failed. Re-trying... Error: '{1}'", name, error || ''); }
|
||||
|
||||
// Add Database Reference dialog strings
|
||||
|
||||
export const addDatabaseReferenceDialogName = localize('addDatabaseReferencedialogName', "Add database reference");
|
||||
|
||||
@@ -14,6 +14,13 @@ import * as mssql from '../../../mssql';
|
||||
import * as vscodeMssql from 'vscode-mssql';
|
||||
import { promises as fs } from 'fs';
|
||||
import { Project } from '../models/project';
|
||||
import * as childProcess from 'child_process';
|
||||
import * as fse from 'fs-extra';
|
||||
|
||||
export interface ValidationResult {
|
||||
errorMessage: string;
|
||||
validated: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidates on the error message string
|
||||
@@ -395,3 +402,79 @@ try {
|
||||
export function getAzdataApi(): typeof azdataType | undefined {
|
||||
return azdataApi;
|
||||
}
|
||||
|
||||
export async function createFolderIfNotExist(folderPath: string): Promise<void> {
|
||||
try {
|
||||
await fse.mkdir(folderPath);
|
||||
} catch {
|
||||
// Ignore if failed
|
||||
}
|
||||
}
|
||||
|
||||
export async function executeCommand(cmd: string, outputChannel: vscode.OutputChannel, timeout: number = 5 * 60 * 1000): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (outputChannel) {
|
||||
outputChannel.appendLine(` > ${cmd}`);
|
||||
}
|
||||
let child = childProcess.exec(cmd, {
|
||||
timeout: timeout
|
||||
}, (err, stdout) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(stdout);
|
||||
}
|
||||
});
|
||||
|
||||
// Add listeners to print stdout and stderr if an output channel was provided
|
||||
|
||||
if (child?.stdout) {
|
||||
child.stdout.on('data', data => { outputDataChunk(outputChannel, data, ' stdout: '); });
|
||||
}
|
||||
if (child?.stderr) {
|
||||
child.stderr.on('data', data => { outputDataChunk(outputChannel, data, ' stderr: '); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function outputDataChunk(outputChannel: vscode.OutputChannel, data: string | Buffer, header: string): void {
|
||||
data.toString().split(/\r?\n/)
|
||||
.forEach(line => {
|
||||
if (outputChannel) {
|
||||
outputChannel.appendLine(header + line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function retry<T>(
|
||||
name: string,
|
||||
attempt: () => Promise<T>,
|
||||
verify: (result: T) => Promise<ValidationResult>,
|
||||
formatResult: (result: T) => Promise<string>,
|
||||
outputChannel: vscode.OutputChannel,
|
||||
numberOfAttempts: number = 10,
|
||||
waitInSeconds: number = 2
|
||||
): Promise<T | undefined> {
|
||||
for (let count = 0; count < numberOfAttempts; count++) {
|
||||
outputChannel.appendLine(constants.retryWaitMessage(waitInSeconds, name));
|
||||
await new Promise(c => setTimeout(c, waitInSeconds * 1000));
|
||||
outputChannel.appendLine(constants.retryRunMessage(count, numberOfAttempts, name));
|
||||
|
||||
try {
|
||||
let result = await attempt();
|
||||
const validationResult = await verify(result);
|
||||
const formattedResult = await formatResult(result);
|
||||
if (validationResult.validated) {
|
||||
outputChannel.appendLine(constants.retrySucceedMessage(name, formattedResult));
|
||||
return result;
|
||||
} else {
|
||||
outputChannel.appendLine(constants.retryFailedMessage(name, formattedResult, validationResult.errorMessage));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
outputChannel.appendLine(constants.retryMessage(name, err));
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ProjectsController } from './projectController';
|
||||
import { NetCoreTool } from '../tools/netcoreTool';
|
||||
import { IconPathHelper } from '../common/iconHelper';
|
||||
import { WorkspaceTreeItem } from 'dataworkspace';
|
||||
import * as constants from '../common/constants';
|
||||
import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider';
|
||||
import { launchAddSqlBindingQuickpick } from '../dialogs/addSqlBindingQuickpick';
|
||||
|
||||
@@ -22,10 +23,11 @@ import { launchAddSqlBindingQuickpick } from '../dialogs/addSqlBindingQuickpick'
|
||||
export default class MainController implements vscode.Disposable {
|
||||
protected projectsController: ProjectsController;
|
||||
protected netcoreTool: NetCoreTool;
|
||||
private _outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel(constants.projectsOutputChannel);
|
||||
|
||||
public constructor(private context: vscode.ExtensionContext) {
|
||||
this.projectsController = new ProjectsController();
|
||||
this.netcoreTool = new NetCoreTool();
|
||||
this.projectsController = new ProjectsController(this._outputChannel);
|
||||
this.netcoreTool = new NetCoreTool(this._outputChannel);
|
||||
}
|
||||
|
||||
public get extensionContext(): vscode.ExtensionContext {
|
||||
@@ -50,6 +52,7 @@ export default class MainController implements vscode.Disposable {
|
||||
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.build', async (node: WorkspaceTreeItem) => { await this.projectsController.buildProject(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.publish', async (node: WorkspaceTreeItem) => { this.projectsController.publishProject(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.deployLocal', async (node: WorkspaceTreeItem) => { this.projectsController.deployProject(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.schemaCompare', async (node: WorkspaceTreeItem) => { await this.projectsController.schemaCompare(node); });
|
||||
vscode.commands.registerCommand('sqlDatabaseProjects.createProjectFromDatabase', async (context: azdataType.IConnectionProfile | vscodeMssql.ITreeNodeInfo | undefined) => { await this.projectsController.createProjectFromDatabase(context); });
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ import { TelemetryActions, TelemetryReporter, TelemetryViews } from '../common/t
|
||||
import { IconPathHelper } from '../common/iconHelper';
|
||||
import { DashboardData, PublishData, Status } from '../models/dashboardData/dashboardData';
|
||||
import { launchPublishDatabaseQuickpick } from '../dialogs/publishDatabaseQuickpick';
|
||||
import { launchDeployDatabaseQuickpick } from '../dialogs/deployDatabaseQuickpick';
|
||||
import { DeployService } from '../models/deploy/deployService';
|
||||
import { SqlTargetPlatform } from 'sqldbproj';
|
||||
import { createNewProjectFromDatabaseWithQuickpick } from '../dialogs/createProjectFromDatabaseQuickpick';
|
||||
import { addDatabaseReferenceQuickpick } from '../dialogs/addDatabaseReferenceQuickpick';
|
||||
@@ -64,12 +66,14 @@ export class ProjectsController {
|
||||
private buildHelper: BuildHelper;
|
||||
private buildInfo: DashboardData[] = [];
|
||||
private publishInfo: PublishData[] = [];
|
||||
private deployService: DeployService;
|
||||
|
||||
projFileWatchers = new Map<string, vscode.FileSystemWatcher>();
|
||||
|
||||
constructor() {
|
||||
this.netCoreTool = new NetCoreTool();
|
||||
constructor(outputChannel: vscode.OutputChannel) {
|
||||
this.netCoreTool = new NetCoreTool(outputChannel);
|
||||
this.buildHelper = new BuildHelper();
|
||||
this.deployService = new DeployService(outputChannel);
|
||||
}
|
||||
|
||||
public getDashboardPublishData(projectFile: string): (string | dataworkspace.IconCellValue)[][] {
|
||||
@@ -251,6 +255,46 @@ export class ProjectsController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploys a project
|
||||
* @param treeNode a treeItem in a project's hierarchy, to be used to obtain a Project
|
||||
*/
|
||||
public async deployProject(context: Project | dataworkspace.WorkspaceTreeItem): Promise<void> {
|
||||
const project: Project = this.getProjectFromContext(context);
|
||||
try {
|
||||
let deployProfile = await launchDeployDatabaseQuickpick(project);
|
||||
if (deployProfile && deployProfile.deploySettings) {
|
||||
let connectionUri: string | undefined;
|
||||
if (deployProfile.localDbSetting) {
|
||||
connectionUri = await this.deployService.deploy(deployProfile, project);
|
||||
if (connectionUri) {
|
||||
deployProfile.deploySettings.connectionUri = connectionUri;
|
||||
}
|
||||
}
|
||||
if (deployProfile.deploySettings.connectionUri) {
|
||||
const publishResult = await this.publishOrScriptProject(project, deployProfile.deploySettings, true);
|
||||
if (publishResult && publishResult.success) {
|
||||
|
||||
// Update app settings if requested by user
|
||||
//
|
||||
await this.deployService.updateAppSettings(deployProfile);
|
||||
if (deployProfile.localDbSetting) {
|
||||
await this.deployService.getConnection(deployProfile.localDbSetting, true, deployProfile.localDbSetting.dbName);
|
||||
}
|
||||
vscode.window.showInformationMessage(constants.deployProjectSucceed);
|
||||
} else {
|
||||
vscode.window.showErrorMessage(constants.deployProjectFailed(publishResult?.errorMessage || ''));
|
||||
}
|
||||
} else {
|
||||
vscode.window.showErrorMessage(constants.deployProjectFailed(constants.deployProjectFailedMessage));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(constants.deployProjectFailed(utils.getErrorMessage(error)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and publishes a project
|
||||
* @param treeNode a treeItem in a project's hierarchy, to be used to obtain a Project
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as constants from '../common/constants';
|
||||
import { AppSettingType, IDeployProfile, ILocalDbSetting } from '../models/deploy/deployProfile';
|
||||
import { Project } from '../models/project';
|
||||
import * as generator from 'generate-password';
|
||||
import { getPublishDatabaseSettings } from './publishDatabaseQuickpick';
|
||||
import * as path from 'path';
|
||||
import * as fse from 'fs-extra';
|
||||
|
||||
/**
|
||||
* Create flow for Deploying a database using only VS Code-native APIs such as QuickPick
|
||||
*/
|
||||
export async function launchDeployDatabaseQuickpick(project: Project): Promise<IDeployProfile | undefined> {
|
||||
|
||||
// Show options to user for deploy to existing server or docker
|
||||
|
||||
const deployOption = await vscode.window.showQuickPick(
|
||||
[constants.deployToExistingServer, constants.deployToDockerContainer],
|
||||
{ title: constants.selectDeployOption, ignoreFocusOut: true });
|
||||
|
||||
// Return when user hits escape
|
||||
if (!deployOption) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let localDbSetting: ILocalDbSetting | undefined;
|
||||
// Deploy to docker selected
|
||||
if (deployOption === constants.deployToDockerContainer) {
|
||||
let portNumber = await vscode.window.showInputBox({
|
||||
title: constants.enterPortNumber,
|
||||
ignoreFocusOut: true,
|
||||
value: constants.defaultPortNumber,
|
||||
validateInput: input => isNaN(+input) ? constants.portMustBeNumber : undefined
|
||||
}
|
||||
);
|
||||
|
||||
// Return when user hits escape
|
||||
if (!portNumber) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let password: string | undefined = generator.generate({
|
||||
length: 10,
|
||||
numbers: true,
|
||||
symbols: true,
|
||||
lowercase: true,
|
||||
uppercase: true,
|
||||
exclude: '`"\'' // Exclude the chars that cannot be included in the password. Some chars can make the command fail in the terminal
|
||||
});
|
||||
password = await vscode.window.showInputBox({
|
||||
title: constants.enterPassword,
|
||||
ignoreFocusOut: true,
|
||||
value: password,
|
||||
password: true
|
||||
}
|
||||
);
|
||||
|
||||
// Return when user hits escape
|
||||
if (!password) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
localDbSetting = {
|
||||
serverName: 'localhost',
|
||||
userName: 'sa',
|
||||
dbName: project.projectFileName,
|
||||
password: password,
|
||||
port: +portNumber,
|
||||
};
|
||||
}
|
||||
let deploySettings = await getPublishDatabaseSettings(project, deployOption !== constants.deployToDockerContainer);
|
||||
|
||||
// Return when user hits escape
|
||||
if (!deploySettings) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// TODO: Ask for SQL CMD Variables or profile
|
||||
|
||||
let envVarName: string | undefined = '';
|
||||
const integrateWithAzureFunctions: boolean = true; //TODO: get value from settings or quickpick
|
||||
|
||||
//TODO: find a better way to find if AF or local settings is in the project
|
||||
//
|
||||
const localSettings = path.join(project.projectFolderPath, constants.azureFunctionLocalSettingsFileName);
|
||||
const settingExist: boolean = await fse.pathExists(localSettings);
|
||||
if (integrateWithAzureFunctions && settingExist) {
|
||||
|
||||
// Ask user to update app settings or not
|
||||
//
|
||||
let choices: { [id: string]: boolean } = {};
|
||||
let options = {
|
||||
placeHolder: constants.appSettingPrompt
|
||||
};
|
||||
choices[constants.yesString] = true;
|
||||
choices[constants.noString] = false;
|
||||
let result = await vscode.window.showQuickPick(Object.keys(choices).map(c => {
|
||||
return {
|
||||
label: c
|
||||
};
|
||||
}), options);
|
||||
|
||||
// Return when user hits escape
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (result !== undefined && choices[result.label] || false) {
|
||||
envVarName = await vscode.window.showInputBox(
|
||||
{
|
||||
title: constants.enterConnectionStringEnvName,
|
||||
ignoreFocusOut: true,
|
||||
value: constants.defaultConnectionStringEnvVarName,
|
||||
validateInput: input => input === '' ? constants.valueCannotBeEmpty : undefined,
|
||||
placeHolder: constants.enterConnectionStringEnvNameDescription
|
||||
}
|
||||
);
|
||||
|
||||
// Return when user hits escape
|
||||
if (!envVarName) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (localDbSetting && deploySettings) {
|
||||
deploySettings.serverName = localDbSetting.serverName;
|
||||
}
|
||||
|
||||
return {
|
||||
localDbSetting: localDbSetting,
|
||||
envVariableName: envVarName,
|
||||
appSettingFile: settingExist ? localSettings : undefined,
|
||||
deploySettings: deploySettings,
|
||||
appSettingType: settingExist ? AppSettingType.AzureFunction : AppSettingType.None
|
||||
};
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import { IDeploySettings } from '../models/IDeploySettings';
|
||||
/**
|
||||
* Create flow for Publishing a database using only VS Code-native APIs such as QuickPick
|
||||
*/
|
||||
export async function launchPublishDatabaseQuickpick(project: Project, projectController: ProjectsController): Promise<void> {
|
||||
export async function getPublishDatabaseSettings(project: Project, promptForConnection: boolean = true): Promise<IDeploySettings | undefined> {
|
||||
|
||||
// 1. Select publish settings file (optional)
|
||||
// Create custom quickpick so we can control stuff like displaying the loading indicator
|
||||
@@ -74,23 +74,26 @@ export async function launchPublishDatabaseQuickpick(project: Project, projectCo
|
||||
quickPick.hide(); // Hide the quickpick immediately so it isn't showing while the API loads
|
||||
|
||||
// 2. Select connection
|
||||
const vscodeMssqlApi = await getVscodeMssqlApi();
|
||||
|
||||
let connectionProfile: IConnectionInfo | undefined = undefined;
|
||||
let connectionUri: string = '';
|
||||
let dbs: string[] | undefined = undefined;
|
||||
while (!dbs) {
|
||||
connectionProfile = await vscodeMssqlApi.promptForConnection(true);
|
||||
if (!connectionProfile) {
|
||||
// User cancelled
|
||||
return;
|
||||
}
|
||||
// Get the list of databases now to validate that the connection is valid and re-prompt them if it isn't
|
||||
try {
|
||||
connectionUri = await vscodeMssqlApi.connect(connectionProfile);
|
||||
dbs = await vscodeMssqlApi.listDatabases(connectionUri);
|
||||
} catch (err) {
|
||||
// no-op, the mssql extension handles showing the error to the user. We'll just go
|
||||
// back and prompt the user for a connection again
|
||||
let dbs: string[] = [];
|
||||
let connectionUri: string | undefined;
|
||||
if (promptForConnection) {
|
||||
const vscodeMssqlApi = await getVscodeMssqlApi();
|
||||
while (!dbs) {
|
||||
connectionProfile = await vscodeMssqlApi.promptForConnection(true);
|
||||
if (!connectionProfile) {
|
||||
// User cancelled
|
||||
return;
|
||||
}
|
||||
// Get the list of databases now to validate that the connection is valid and re-prompt them if it isn't
|
||||
try {
|
||||
connectionUri = await vscodeMssqlApi.connect(connectionProfile);
|
||||
dbs = await vscodeMssqlApi.listDatabases(connectionUri);
|
||||
} catch (err) {
|
||||
// no-op, the mssql extension handles showing the error to the user. We'll just go
|
||||
// back and prompt the user for a connection again
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,22 +187,33 @@ export async function launchPublishDatabaseQuickpick(project: Project, projectCo
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Select action to take
|
||||
const action = await vscode.window.showQuickPick(
|
||||
[constants.generateScriptButtonText, constants.publish],
|
||||
{ title: constants.chooseAction, ignoreFocusOut: true });
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 6. Generate script/publish
|
||||
let settings: IDeploySettings = {
|
||||
databaseName: databaseName,
|
||||
serverName: connectionProfile!.server,
|
||||
connectionUri: connectionUri,
|
||||
serverName: connectionProfile?.server || '',
|
||||
connectionUri: connectionUri || '',
|
||||
sqlCmdVariables: sqlCmdVariables,
|
||||
deploymentOptions: await getDefaultPublishDeploymentOptions(project),
|
||||
profileUsed: !!publishProfile
|
||||
};
|
||||
await projectController.publishOrScriptProject(project, settings, action === constants.publish);
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create flow for Publishing a database using only VS Code-native APIs such as QuickPick
|
||||
*/
|
||||
export async function launchPublishDatabaseQuickpick(project: Project, projectController: ProjectsController): Promise<void> {
|
||||
let settings: IDeploySettings | undefined = await getPublishDatabaseSettings(project);
|
||||
|
||||
if (settings) {
|
||||
// 5. Select action to take
|
||||
const action = await vscode.window.showQuickPick(
|
||||
[constants.generateScriptButtonText, constants.publish],
|
||||
{ title: constants.chooseAction, ignoreFocusOut: true });
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
await projectController.publishOrScriptProject(project, settings, action === constants.publish);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { IDeploySettings } from '../IDeploySettings';
|
||||
|
||||
export enum AppSettingType {
|
||||
None,
|
||||
AzureFunction
|
||||
}
|
||||
export interface IDeployProfile {
|
||||
localDbSetting?: ILocalDbSetting;
|
||||
deploySettings?: IDeploySettings;
|
||||
envVariableName?: string;
|
||||
appSettingFile?: string;
|
||||
appSettingType: AppSettingType;
|
||||
}
|
||||
|
||||
export interface ILocalDbSetting {
|
||||
serverName: string,
|
||||
port: number,
|
||||
userName: string,
|
||||
password: string,
|
||||
dbName: string,
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AppSettingType, IDeployProfile, ILocalDbSetting } from './deployProfile';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
import { Project } from '../project';
|
||||
import * as constants from '../../common/constants';
|
||||
import * as utils from '../../common/utils';
|
||||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as os from 'os';
|
||||
import { ConnectionResult } from 'azdata';
|
||||
import * as templates from '../../templates/templates';
|
||||
|
||||
export class DeployService {
|
||||
|
||||
constructor(private _outputChannel: vscode.OutputChannel) {
|
||||
}
|
||||
|
||||
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: IDeployProfile, appSettingContent: any): string | undefined {
|
||||
switch (profile.appSettingType) {
|
||||
case AppSettingType.AzureFunction:
|
||||
return <string>appSettingContent?.Values['FUNCTIONS_WORKER_RUNTIME'];
|
||||
default:
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async updateAppSettings(profile: IDeployProfile): 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 (profile.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': profile?.localDbSetting?.serverName || '',
|
||||
'PORT': profile?.localDbSetting?.port?.toString() || '',
|
||||
'USER': profile?.localDbSetting?.userName || '',
|
||||
'SA_PASSWORD': profile?.localDbSetting?.password || '',
|
||||
'DATABASE': profile?.localDbSetting?.dbName || '',
|
||||
};
|
||||
|
||||
connectionString = templates.macroExpansion(connectionStringTemplate, macroDict);
|
||||
} else if (profile.deploySettings?.connectionUri) {
|
||||
connectionString = await this.getConnectionString(profile.deploySettings?.connectionUri);
|
||||
}
|
||||
|
||||
if (connectionString && profile.envVariableName) {
|
||||
content.Values[profile.envVariableName] = connectionString;
|
||||
await 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async deploy(profile: IDeployProfile, project: Project): Promise<string | undefined> {
|
||||
return await this.executeTask(constants.deployDbTaskName, async () => {
|
||||
if (!profile.localDbSetting) {
|
||||
return undefined;
|
||||
}
|
||||
const projectName = project.projectFileName;
|
||||
const imageLabel = `${constants.dockerImageLabelPrefix}_${projectName}`;
|
||||
const imageName = `${constants.dockerImageNamePrefix}-${projectName}-${UUID.generateUuid().toLowerCase()}`;
|
||||
const root = project.projectFolderPath;
|
||||
const mssqlFolderPath = path.join(root, constants.mssqlFolderName);
|
||||
const commandsFolderPath = path.join(mssqlFolderPath, constants.commandsFolderName);
|
||||
const dockerFilePath = path.join(mssqlFolderPath, constants.dockerFileName);
|
||||
const startFilePath = path.join(commandsFolderPath, constants.startCommandName);
|
||||
|
||||
this.logToOutput(constants.cleaningDockerImagesMessage);
|
||||
// Clean up existing docker image
|
||||
|
||||
await this.cleanDockerObjects(`docker ps -q -a --filter label=${imageLabel}`, ['docker stop', 'docker rm']);
|
||||
await this.cleanDockerObjects(`docker images -f label=${imageLabel} -q`, [`docker rmi -f `]);
|
||||
|
||||
this.logToOutput(constants.creatingDeploymentSettingsMessage);
|
||||
// Create commands
|
||||
//
|
||||
|
||||
await this.createCommands(mssqlFolderPath, commandsFolderPath, dockerFilePath, startFilePath, imageLabel);
|
||||
|
||||
this.logToOutput(constants.runningDockerMessage);
|
||||
// Building the image and running the docker
|
||||
//
|
||||
const createdDockerId: string | undefined = await this.buildAndRunDockerContainer(dockerFilePath, imageName, root, profile.localDbSetting, imageLabel);
|
||||
this.logToOutput(`Docker container created. Id: ${createdDockerId}`);
|
||||
|
||||
|
||||
// Waiting a bit to make sure docker container doesn't crash
|
||||
//
|
||||
const runningDockerId = await utils.retry('Validating the docker container', async () => {
|
||||
return await utils.executeCommand(`docker ps -q -a --filter label=${imageLabel} -q`, this._outputChannel);
|
||||
}, (dockerId) => {
|
||||
return Promise.resolve({ validated: dockerId !== undefined, errorMessage: constants.dockerContainerNotRunningErrorMessage });
|
||||
}, (dockerId) => {
|
||||
return Promise.resolve(dockerId);
|
||||
}, this._outputChannel
|
||||
);
|
||||
|
||||
if (runningDockerId) {
|
||||
this.logToOutput(constants.dockerContainerCreatedMessage(runningDockerId));
|
||||
return await this.getConnection(profile.localDbSetting, false, 'master');
|
||||
|
||||
} else {
|
||||
this.logToOutput(constants.dockerContainerFailedToRunErrorMessage);
|
||||
if (createdDockerId) {
|
||||
// Get the docker logs if docker was created but crashed
|
||||
await utils.executeCommand(constants.dockerLogMessage(createdDockerId), this._outputChannel);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private async buildAndRunDockerContainer(dockerFilePath: string, imageName: string, root: string, profile: ILocalDbSetting, imageLabel: string): Promise<string | undefined> {
|
||||
this.logToOutput('Building docker image ...');
|
||||
await utils.executeCommand(`docker pull ${constants.dockerBaseImage}`, this._outputChannel);
|
||||
await utils.executeCommand(`docker build -f ${dockerFilePath} -t ${imageName} ${root}`, this._outputChannel);
|
||||
await utils.executeCommand(`docker images --filter label=${imageLabel}`, this._outputChannel);
|
||||
|
||||
this.logToOutput('Running docker container ...');
|
||||
await utils.executeCommand(`docker run -p ${profile.port}:1433 -e "MSSQL_SA_PASSWORD=${profile.password}" -d ${imageName}`, this._outputChannel);
|
||||
return await utils.executeCommand(`docker ps -q -a --filter label=${imageLabel} -q`, this._outputChannel);
|
||||
}
|
||||
|
||||
private async getConnectionString(connectionUri: string): Promise<string | undefined> {
|
||||
const getAzdataApi = await utils.getAzdataApi();
|
||||
if (getAzdataApi) {
|
||||
const connection = await getAzdataApi.connection.getConnection(connectionUri);
|
||||
if (connection) {
|
||||
return await getAzdataApi.connection.getConnectionString(connection.connectionId, true);
|
||||
}
|
||||
}
|
||||
// TODO: vscode connections string
|
||||
|
||||
return undefined;
|
||||
|
||||
}
|
||||
|
||||
// Connects to a database
|
||||
private async connectToDatabase(profile: ILocalDbSetting, savePassword: boolean, database: string): Promise<ConnectionResult | string | undefined> {
|
||||
const getAzdataApi = await utils.getAzdataApi();
|
||||
const vscodeMssqlApi = getAzdataApi ? undefined : await utils.getVscodeMssqlApi();
|
||||
if (getAzdataApi) {
|
||||
const connectionProfile = {
|
||||
password: profile.password,
|
||||
serverName: `${profile.serverName},${profile.port}`,
|
||||
database: database,
|
||||
savePassword: savePassword,
|
||||
userName: profile.userName,
|
||||
providerName: 'MSSQL',
|
||||
saveProfile: false,
|
||||
id: '',
|
||||
connectionName: `${constants.connectionNamePrefix} ${profile.dbName}`,
|
||||
options: [],
|
||||
authenticationType: 'SqlLogin'
|
||||
};
|
||||
return await getAzdataApi.connection.connect(connectionProfile, false, false);
|
||||
} else if (vscodeMssqlApi) {
|
||||
const connectionProfile = {
|
||||
password: profile.password,
|
||||
server: `${profile.serverName}`,
|
||||
port: profile.port,
|
||||
database: database,
|
||||
savePassword: savePassword,
|
||||
user: profile.userName,
|
||||
authenticationType: 'SqlLogin',
|
||||
encrypt: false,
|
||||
connectTimeout: 30,
|
||||
applicationName: 'SQL Database Project',
|
||||
accountId: undefined,
|
||||
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
|
||||
};
|
||||
let connectionUrl = await vscodeMssqlApi.connect(connectionProfile);
|
||||
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 getAzdataApi = await utils.getAzdataApi();
|
||||
if (!connection) {
|
||||
return { validated: false, errorMessage: constants.connectionFailedError('No result returned') };
|
||||
} else if (getAzdataApi) {
|
||||
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 getAzdataApi = await utils.getAzdataApi();
|
||||
const connectionResult = connection !== undefined && getAzdataApi ? <ConnectionResult>connection : undefined;
|
||||
return connectionResult ? connectionResult.connectionId : <string>connection;
|
||||
}
|
||||
|
||||
public async getConnection(profile: ILocalDbSetting, savePassword: boolean, database: string, timeoutInSeconds: number = 5): Promise<string | undefined> {
|
||||
const getAzdataApi = await utils.getAzdataApi();
|
||||
let connection = await utils.retry(
|
||||
constants.connectingToSqlServerOnDockerMessage,
|
||||
async () => {
|
||||
return await this.connectToDatabase(profile, savePassword, database);
|
||||
},
|
||||
this.validateConnection,
|
||||
this.formatConnectionResult,
|
||||
this._outputChannel,
|
||||
5, timeoutInSeconds);
|
||||
|
||||
if (connection) {
|
||||
const connectionResult = <ConnectionResult>connection;
|
||||
if (getAzdataApi) {
|
||||
return await getAzdataApi.connection.getUriForConnection(connectionResult.connectionId);
|
||||
} else {
|
||||
return <string>connection;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async executeTask<T>(taskName: string, task: () => Promise<T>): Promise<T> {
|
||||
const getAzdataApi = await utils.getAzdataApi();
|
||||
if (getAzdataApi) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
let msgTaskName = taskName;
|
||||
getAzdataApi!.tasks.startBackgroundOperation({
|
||||
displayName: msgTaskName,
|
||||
description: msgTaskName,
|
||||
isCancelable: false,
|
||||
operation: async op => {
|
||||
try {
|
||||
let result: T = await task();
|
||||
|
||||
op.updateStatus(getAzdataApi!.TaskStatus.Succeeded);
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
let errorMsg = constants.taskFailedError(taskName, error ? error.message : '');
|
||||
op.updateStatus(getAzdataApi!.TaskStatus.Failed, errorMsg);
|
||||
reject(errorMsg);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return await task();
|
||||
}
|
||||
}
|
||||
|
||||
private logToOutput(message: string): void {
|
||||
this._outputChannel.appendLine(message);
|
||||
}
|
||||
|
||||
// Creates command file and docker file needed for deploy operation
|
||||
private async createCommands(mssqlFolderPath: string, commandsFolderPath: string, dockerFilePath: string, startFilePath: string, imageLabel: string): Promise<void> {
|
||||
// Create mssql folders if doesn't exist
|
||||
//
|
||||
await utils.createFolderIfNotExist(mssqlFolderPath);
|
||||
await utils.createFolderIfNotExist(commandsFolderPath);
|
||||
|
||||
// Start command
|
||||
//
|
||||
await this.createFile(startFilePath, 'echo starting the container!');
|
||||
if (os.platform() !== 'win32') {
|
||||
await utils.executeCommand(`chmod +x ${startFilePath}`, this._outputChannel);
|
||||
}
|
||||
|
||||
// Create the Dockerfile
|
||||
//
|
||||
await this.createFile(dockerFilePath,
|
||||
`
|
||||
FROM ${constants.dockerBaseImage}
|
||||
ENV ACCEPT_EULA=Y
|
||||
ENV MSSQL_PID=Developer
|
||||
LABEL ${imageLabel}
|
||||
RUN mkdir -p /opt/sqlproject
|
||||
COPY ${constants.mssqlFolderName}/${constants.commandsFolderName}/ /opt/commands
|
||||
RUN ["/bin/bash", "/opt/commands/start.sh"]
|
||||
`);
|
||||
}
|
||||
|
||||
private async createFile(filePath: string, content: string): Promise<void> {
|
||||
this.logToOutput(`Creating file ${filePath}, content:${content}`);
|
||||
await fse.writeFile(filePath, content);
|
||||
}
|
||||
|
||||
public async cleanDockerObjects(commandToGetObjects: string, commandsToClean: string[]): Promise<void> {
|
||||
const currentIds = await utils.executeCommand(commandToGetObjects, this._outputChannel);
|
||||
if (currentIds) {
|
||||
const ids = currentIds.split(/\r?\n/);
|
||||
for (let index = 0; index < ids.length; index++) {
|
||||
const id = ids[index];
|
||||
if (id) {
|
||||
for (let commandId = 0; commandId < commandsToClean.length; commandId++) {
|
||||
const command = commandsToClean[commandId];
|
||||
await utils.executeCommand(`${command} ${id}`, this._outputChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sinon from 'sinon';
|
||||
import * as baselines from '../baselines/baselines';
|
||||
import * as testUtils from '../testUtils';
|
||||
import { DeployService } from '../../models/deploy/deployService';
|
||||
import { Project } from '../../models/project';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as childProcess from 'child_process';
|
||||
import { AppSettingType, IDeployProfile } from '../../models/deploy/deployProfile';
|
||||
let fse = require('fs-extra');
|
||||
let path = require('path');
|
||||
|
||||
export interface TestContext {
|
||||
outputChannel: vscode.OutputChannel;
|
||||
}
|
||||
|
||||
export const mockConnectionResult: azdata.ConnectionResult = {
|
||||
connected: true,
|
||||
connectionId: 'id',
|
||||
errorMessage: '',
|
||||
errorCode: 0
|
||||
};
|
||||
|
||||
export const mockFailedConnectionResult: azdata.ConnectionResult = {
|
||||
connected: false,
|
||||
connectionId: 'id',
|
||||
errorMessage: 'Failed to connect',
|
||||
errorCode: 0
|
||||
};
|
||||
|
||||
export function createContext(): TestContext {
|
||||
return {
|
||||
outputChannel: {
|
||||
name: '',
|
||||
append: () => { },
|
||||
appendLine: () => { },
|
||||
clear: () => { },
|
||||
show: () => { },
|
||||
hide: () => { },
|
||||
dispose: () => { }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let sandbox: sinon.SinonSandbox;
|
||||
|
||||
describe('deploy service', function (): void {
|
||||
before(async function (): Promise<void> {
|
||||
await baselines.loadBaselines();
|
||||
});
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
it('Should deploy a database to docker container successfully', async function (): Promise<void> {
|
||||
const testContext = createContext();
|
||||
const deployProfile: IDeployProfile = {
|
||||
appSettingType: AppSettingType.AzureFunction,
|
||||
appSettingFile: '',
|
||||
deploySettings: undefined,
|
||||
envVariableName: '',
|
||||
localDbSetting: {
|
||||
dbName: 'test',
|
||||
password: 'PLACEHOLDER',
|
||||
port: 1433,
|
||||
serverName: 'localhost',
|
||||
userName: 'sa'
|
||||
}
|
||||
};
|
||||
const projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
|
||||
const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath);
|
||||
const deployService = new DeployService(testContext.outputChannel);
|
||||
sandbox.stub(azdata.connection, 'connect').returns(Promise.resolve(mockConnectionResult));
|
||||
sandbox.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve('connection'));
|
||||
sandbox.stub(azdata.tasks, 'startBackgroundOperation').callThrough();
|
||||
sandbox.stub(childProcess, 'exec').yields(undefined, 'id');
|
||||
let connection = await deployService.deploy(deployProfile, project1);
|
||||
should(connection).equals('connection');
|
||||
|
||||
});
|
||||
|
||||
it('Should retry connecting to the server', async function (): Promise<void> {
|
||||
const testContext = createContext();
|
||||
const localDbSettings = {
|
||||
dbName: 'test',
|
||||
password: 'PLACEHOLDER',
|
||||
port: 1433,
|
||||
serverName: 'localhost',
|
||||
userName: 'sa'
|
||||
};
|
||||
|
||||
const deployService = new DeployService(testContext.outputChannel);
|
||||
let connectionStub = sandbox.stub(azdata.connection, 'connect');
|
||||
connectionStub.onFirstCall().returns(Promise.resolve(mockFailedConnectionResult));
|
||||
connectionStub.onSecondCall().returns(Promise.resolve(mockConnectionResult));
|
||||
sandbox.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve('connection'));
|
||||
sandbox.stub(azdata.tasks, 'startBackgroundOperation').callThrough();
|
||||
sandbox.stub(childProcess, 'exec').yields(undefined, 'id');
|
||||
let connection = await deployService.getConnection(localDbSettings, false, 'master', 2);
|
||||
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: IDeployProfile = {
|
||||
appSettingType: AppSettingType.AzureFunction,
|
||||
appSettingFile: filePath,
|
||||
deploySettings: undefined,
|
||||
envVariableName: 'SQLConnectionString',
|
||||
localDbSetting: {
|
||||
dbName: 'test',
|
||||
password: 'PLACEHOLDER',
|
||||
port: 1433,
|
||||
serverName: 'localhost',
|
||||
userName: 'sa'
|
||||
}
|
||||
};
|
||||
|
||||
const deployService = new DeployService(testContext.outputChannel);
|
||||
sandbox.stub(childProcess, 'exec').yields(undefined, 'id');
|
||||
await deployService.updateAppSettings(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: IDeployProfile = {
|
||||
appSettingType: AppSettingType.AzureFunction,
|
||||
appSettingFile: filePath,
|
||||
deploySettings: {
|
||||
connectionUri: 'connection',
|
||||
databaseName: 'test',
|
||||
serverName: 'test'
|
||||
},
|
||||
envVariableName: 'SQLConnectionString',
|
||||
localDbSetting: undefined
|
||||
};
|
||||
|
||||
const deployService = new DeployService(testContext.outputChannel);
|
||||
let connection = new azdata.connection.ConnectionProfile();
|
||||
sandbox.stub(azdata.connection, 'getConnection').returns(Promise.resolve(connection));
|
||||
sandbox.stub(childProcess, 'exec').yields(undefined, 'id');
|
||||
sandbox.stub(azdata.connection, 'getConnectionString').returns(Promise.resolve('connectionString'));
|
||||
await deployService.updateAppSettings(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> {
|
||||
const testContext = createContext();
|
||||
const deployService = new DeployService(testContext.outputChannel);
|
||||
|
||||
let process = sandbox.stub(childProcess, 'exec').yields(undefined, `
|
||||
id
|
||||
id2
|
||||
id3`);
|
||||
|
||||
await deployService.cleanDockerObjects(`docker ps -q -a --filter label=test`, ['docker stop', 'docker rm']);
|
||||
should(process.calledThrice);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,16 +17,18 @@ import { Project } from '../../models/project';
|
||||
import { ProjectsController } from '../../controllers/projectController';
|
||||
import { IDeploySettings } from '../../models/IDeploySettings';
|
||||
import { emptySqlDatabaseProjectTypeId } from '../../common/constants';
|
||||
import { mockDacFxOptionsResult } from '../testContext';
|
||||
import { createContext, mockDacFxOptionsResult, TestContext } from '../testContext';
|
||||
|
||||
let testContext: TestContext;
|
||||
describe('Publish Database Dialog', () => {
|
||||
before(async function (): Promise<void> {
|
||||
await templates.loadTemplates(path.join(__dirname, '..', '..', '..', 'resources', 'templates'));
|
||||
await baselines.loadBaselines();
|
||||
testContext = createContext();
|
||||
});
|
||||
|
||||
it('Should open dialog successfully ', async function (): Promise<void> {
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const projFileDir = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`);
|
||||
|
||||
const projFilePath = await projController.createNewProject({
|
||||
@@ -43,7 +45,7 @@ describe('Publish Database Dialog', () => {
|
||||
});
|
||||
|
||||
it('Should create default database name correctly ', async function (): Promise<void> {
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const projFolder = `TestProject_${new Date().getTime()}`;
|
||||
const projFileDir = path.join(os.tmpdir(), projFolder);
|
||||
|
||||
|
||||
@@ -13,17 +13,24 @@ import { NetCoreTool, DBProjectConfigurationKey, NetCoreInstallLocationKey, NetC
|
||||
import { getQuotedPath } from '../common/utils';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { generateTestFolderPath } from './testUtils';
|
||||
import { createContext, TestContext } from './testContext';
|
||||
|
||||
let testContext: TestContext;
|
||||
|
||||
describe('NetCoreTool: Net core tests', function (): void {
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
beforeEach(function (): void {
|
||||
testContext = createContext();
|
||||
});
|
||||
|
||||
it('Should override dotnet default value with settings', async function (): Promise<void> {
|
||||
try {
|
||||
// update settings and validate
|
||||
await vscode.workspace.getConfiguration(DBProjectConfigurationKey).update(NetCoreInstallLocationKey, 'test value path', true);
|
||||
const netcoreTool = new NetCoreTool();
|
||||
const netcoreTool = new NetCoreTool(testContext.outputChannel);
|
||||
sinon.stub(netcoreTool, 'showInstallDialog').returns(Promise.resolve());
|
||||
should(netcoreTool.netcoreInstallLocation).equal('test value path'); // the path in settings should be taken
|
||||
should(await netcoreTool.findOrInstallNetCore()).equal(false); // dotnet can not be present at dummy path in settings
|
||||
@@ -35,7 +42,7 @@ describe('NetCoreTool: Net core tests', function (): void {
|
||||
});
|
||||
|
||||
it('Should find right dotnet default paths', async function (): Promise<void> {
|
||||
const netcoreTool = new NetCoreTool();
|
||||
const netcoreTool = new NetCoreTool(testContext.outputChannel);
|
||||
sinon.stub(netcoreTool, 'showInstallDialog').returns(Promise.resolve());
|
||||
await netcoreTool.findOrInstallNetCore();
|
||||
|
||||
@@ -53,7 +60,7 @@ describe('NetCoreTool: Net core tests', function (): void {
|
||||
});
|
||||
|
||||
it('should run a command successfully', async function (): Promise<void> {
|
||||
const netcoreTool = new NetCoreTool();
|
||||
const netcoreTool = new NetCoreTool(testContext.outputChannel);
|
||||
const dummyFile = path.join(await generateTestFolderPath(), 'dummy.dacpac');
|
||||
const outputChannel = vscode.window.createOutputChannel('db project test');
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('ProjectsController', function (): void {
|
||||
describe('project controller operations', function (): void {
|
||||
describe('Project file operations and prompting', function (): void {
|
||||
it('Should create new sqlproj file with correct values', async function (): Promise<void> {
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const projFileDir = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`);
|
||||
|
||||
const projFilePath = await projController.createNewProject({
|
||||
@@ -67,7 +67,7 @@ describe('ProjectsController', function (): void {
|
||||
});
|
||||
|
||||
it('Should create new sqlproj file with correct specified target platform', async function (): Promise<void> {
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const projFileDir = path.join(os.tmpdir(), `TestProject_${new Date().getTime()}`);
|
||||
const projTargetPlatform = SqlTargetPlatform.sqlAzure; // default is SQL Server 2019
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('ProjectsController', function (): void {
|
||||
for (const name of ['', ' ', undefined]) {
|
||||
const showInputBoxStub = sinon.stub(vscode.window, 'showInputBox').resolves(name);
|
||||
const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const project = new Project('FakePath');
|
||||
|
||||
should(project.files.length).equal(0);
|
||||
@@ -105,7 +105,7 @@ describe('ProjectsController', function (): void {
|
||||
const tableName = 'table1';
|
||||
sinon.stub(vscode.window, 'showInputBox').resolves(tableName);
|
||||
const spy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const project = await testUtils.createTestProject(baselines.newProjectFileBaseline);
|
||||
|
||||
should(project.files.length).equal(0, 'There should be no files');
|
||||
@@ -121,7 +121,7 @@ describe('ProjectsController', function (): void {
|
||||
const folderName = 'folder1';
|
||||
const stub = sinon.stub(vscode.window, 'showInputBox').resolves(folderName);
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const project = await testUtils.createTestProject(baselines.newProjectFileBaseline);
|
||||
const projectRoot = new ProjectRootTreeItem(project);
|
||||
|
||||
@@ -141,7 +141,7 @@ describe('ProjectsController', function (): void {
|
||||
const folderName = 'folder1';
|
||||
const stub = sinon.stub(vscode.window, 'showInputBox').resolves(folderName);
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const project = await testUtils.createTestProject(baselines.openProjectFileBaseline);
|
||||
const projectRoot = new ProjectRootTreeItem(project);
|
||||
|
||||
@@ -180,7 +180,7 @@ describe('ProjectsController', function (): void {
|
||||
const setupResult = await setupDeleteExcludeTest(proj);
|
||||
const scriptEntry = setupResult[0], projTreeRoot = setupResult[1], preDeployEntry = setupResult[2], postDeployEntry = setupResult[3], noneEntry = setupResult[4];
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
|
||||
await projController.delete(createWorkspaceTreeItem(projTreeRoot.children.find(x => x.friendlyName === 'UpperFolder')!.children[0]) /* LowerFolder */);
|
||||
await projController.delete(createWorkspaceTreeItem(projTreeRoot.children.find(x => x.friendlyName === 'anotherScript.sql')!));
|
||||
@@ -206,7 +206,7 @@ describe('ProjectsController', function (): void {
|
||||
it('Should delete database references', async function (): Promise<void> {
|
||||
// setup - openProject baseline has a system db reference to master
|
||||
const proj = await testUtils.createTestProject(baselines.openProjectFileBaseline);
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.yesString));
|
||||
|
||||
// add dacpac reference
|
||||
@@ -241,7 +241,7 @@ describe('ProjectsController', function (): void {
|
||||
const setupResult = await setupDeleteExcludeTest(proj);
|
||||
const scriptEntry = setupResult[0], projTreeRoot = setupResult[1], preDeployEntry = setupResult[2], postDeployEntry = setupResult[3], noneEntry = setupResult[4];
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
|
||||
await projController.exclude(createWorkspaceTreeItem(<FolderNode>projTreeRoot.children.find(x => x.friendlyName === 'UpperFolder')!.children[0]) /* LowerFolder */);
|
||||
await projController.exclude(createWorkspaceTreeItem(<FileNode>projTreeRoot.children.find(x => x.friendlyName === 'anotherScript.sql')!));
|
||||
@@ -272,7 +272,7 @@ describe('ProjectsController', function (): void {
|
||||
const upperFolder = projTreeRoot.children.find(x => x.friendlyName === 'UpperFolder')!;
|
||||
const lowerFolder = upperFolder.children.find(x => x.friendlyName === 'LowerFolder')!;
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
|
||||
// Exclude files under LowerFolder
|
||||
await projController.exclude(createWorkspaceTreeItem(<FileNode>lowerFolder.children.find(x => x.friendlyName === 'someScript.sql')!));
|
||||
@@ -296,7 +296,7 @@ describe('ProjectsController', function (): void {
|
||||
const folderPath = await testUtils.generateTestFolderPath();
|
||||
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline, folderPath);
|
||||
const treeProvider = new SqlDatabaseProjectTreeViewProvider();
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const project = await Project.openProject(vscode.Uri.file(sqlProjPath).fsPath);
|
||||
treeProvider.load([project]);
|
||||
|
||||
@@ -319,7 +319,7 @@ describe('ProjectsController', function (): void {
|
||||
const preDeployScriptName = 'PreDeployScript1.sql';
|
||||
const postDeployScriptName = 'PostDeployScript1.sql';
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const project = await testUtils.createTestProject(baselines.newProjectFileBaseline);
|
||||
|
||||
sinon.stub(vscode.window, 'showInputBox').resolves(preDeployScriptName);
|
||||
@@ -337,7 +337,7 @@ describe('ProjectsController', function (): void {
|
||||
it('Should change target platform', async function (): Promise<void> {
|
||||
sinon.stub(vscode.window, 'showQuickPick').resolves({ label: SqlTargetPlatform.sqlAzure });
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline);
|
||||
const project = await Project.openProject(sqlProjPath);
|
||||
should(project.getProjectTargetVersion()).equal(constants.targetPlatformToVersion.get(SqlTargetPlatform.sqlServer2019));
|
||||
@@ -447,7 +447,7 @@ describe('ProjectsController', function (): void {
|
||||
it('Should create list of all files and folders correctly', async function (): Promise<void> {
|
||||
const testFolderPath = await testUtils.createDummyFileStructure();
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
const fileList = await projController.generateList(testFolderPath);
|
||||
|
||||
should(fileList.length).equal(15); // Parent folder + 2 files under parent folder + 2 directories with 5 files each
|
||||
@@ -459,7 +459,7 @@ describe('ProjectsController', function (): void {
|
||||
let testFolderPath = await testUtils.generateTestFolderPath();
|
||||
testFolderPath += '_nonexistentFolder'; // Modify folder path to point to a nonexistent location
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
|
||||
await projController.generateList(testFolderPath);
|
||||
should(spy.calledOnce).be.true('showErrorMessage should have been called');
|
||||
@@ -521,7 +521,7 @@ describe('ProjectsController', function (): void {
|
||||
let importPath;
|
||||
let model: ImportDataModel = { connectionUri: 'My Id', database: 'My Database', projName: projectName, filePath: folderPath, version: '1.0.0.0', extractTarget: mssql.ExtractTarget['file'] };
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
projController.setFilePath(model);
|
||||
importPath = model.filePath;
|
||||
|
||||
@@ -534,7 +534,7 @@ describe('ProjectsController', function (): void {
|
||||
let importPath;
|
||||
let model: ImportDataModel = { connectionUri: 'My Id', database: 'My Database', projName: projectName, filePath: folderPath, version: '1.0.0.0', extractTarget: mssql.ExtractTarget['schemaObjectType'] };
|
||||
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
projController.setFilePath(model);
|
||||
importPath = model.filePath;
|
||||
|
||||
@@ -591,7 +591,7 @@ describe('ProjectsController', function (): void {
|
||||
it('Should not allow adding circular project references', async function (): Promise<void> {
|
||||
const projPath1 = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline);
|
||||
const projPath2 = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
|
||||
const project1 = await Project.openProject(vscode.Uri.file(projPath1).fsPath);
|
||||
const project2 = await Project.openProject(vscode.Uri.file(projPath2).fsPath);
|
||||
@@ -623,7 +623,7 @@ describe('ProjectsController', function (): void {
|
||||
|
||||
it('Should add dacpac references as relative paths', async function (): Promise<void> {
|
||||
const projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
|
||||
const projController = new ProjectsController();
|
||||
const projController = new ProjectsController(testContext.outputChannel);
|
||||
|
||||
const project1 = await Project.openProject(vscode.Uri.file(projFilePath).fsPath);
|
||||
const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage');
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as mssql from '../../../mssql/src/mssql';
|
||||
export interface TestContext {
|
||||
context: vscode.ExtensionContext;
|
||||
dacFxService: TypeMoq.IMock<mssql.IDacFxService>;
|
||||
outputChannel: vscode.OutputChannel;
|
||||
}
|
||||
|
||||
export const mockDacFxResult = {
|
||||
@@ -147,7 +148,16 @@ export function createContext(): TestContext {
|
||||
secrets: undefined as any,
|
||||
extension: undefined as any
|
||||
},
|
||||
dacFxService: TypeMoq.Mock.ofType(MockDacFxService)
|
||||
dacFxService: TypeMoq.Mock.ofType(MockDacFxService),
|
||||
outputChannel: {
|
||||
name: '',
|
||||
append: () => { },
|
||||
appendLine: () => { },
|
||||
clear: () => { },
|
||||
show: () => { },
|
||||
hide: () => { },
|
||||
dispose: () => { }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as semver from 'semver';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DoNotAskAgain, InstallNetCore, NetCoreInstallationConfirmation, NetCoreSupportedVersionInstallationConfirmation, projectsOutputChannel, UpdateNetCoreLocation } from '../common/constants';
|
||||
import { DoNotAskAgain, InstallNetCore, NetCoreInstallationConfirmation, NetCoreSupportedVersionInstallationConfirmation, UpdateNetCoreLocation } from '../common/constants';
|
||||
import * as utils from '../common/utils';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -39,10 +39,8 @@ export interface DotNetCommandOptions {
|
||||
commandTitle?: string;
|
||||
argument?: string;
|
||||
}
|
||||
|
||||
export class NetCoreTool {
|
||||
|
||||
private static _outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel(projectsOutputChannel);
|
||||
private osPlatform: string = os.platform();
|
||||
private netCoreSdkInstalledVersion: string | undefined;
|
||||
private netCoreInstallState: netCoreInstallState = netCoreInstallState.netCoreVersionSupported;
|
||||
@@ -63,6 +61,10 @@ export class NetCoreTool {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
constructor(private _outputChannel: vscode.OutputChannel) {
|
||||
}
|
||||
|
||||
public async showInstallDialog(): Promise<void> {
|
||||
let result;
|
||||
if (this.netCoreInstallState === netCoreInstallState.netCoreNotPresent) {
|
||||
@@ -183,7 +185,7 @@ export class NetCoreTool {
|
||||
|
||||
public async runDotnetCommand(options: DotNetCommandOptions): Promise<string> {
|
||||
if (options && options.commandTitle !== undefined && options.commandTitle !== null) {
|
||||
NetCoreTool._outputChannel.appendLine(`\t[ ${options.commandTitle} ]`);
|
||||
this._outputChannel.appendLine(`\t[ ${options.commandTitle} ]`);
|
||||
}
|
||||
|
||||
if (!(await this.findOrInstallNetCore())) {
|
||||
@@ -198,9 +200,9 @@ export class NetCoreTool {
|
||||
const command = dotnetPath + ' ' + options.argument;
|
||||
|
||||
try {
|
||||
return await this.runStreamedCommand(command, NetCoreTool._outputChannel, options);
|
||||
return await this.runStreamedCommand(command, this._outputChannel, options);
|
||||
} catch (error) {
|
||||
NetCoreTool._outputChannel.append(localize('sqlDatabaseProject.RunCommand.ErroredOut', "\t>>> {0} … errored out: {1}", command, utils.getErrorMessage(error))); //errors are localized in our code where emitted, other errors are pass through from external components that are not easily localized
|
||||
this._outputChannel.append(localize('sqlDatabaseProject.RunCommand.ErroredOut', "\t>>> {0} … errored out: {1}", command, utils.getErrorMessage(error))); //errors are localized in our code where emitted, other errors are pass through from external components that are not easily localized
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ declare module 'vscode-mssql' {
|
||||
* @param connectionInfo The connection info
|
||||
* @returns The URI associated with this connection
|
||||
*/
|
||||
connect(connectionInfo: IConnectionInfo): Promise<string>;
|
||||
connect(connectionInfo: IConnectionInfo): Promise<string>;
|
||||
|
||||
/**
|
||||
* Lists the databases for a given connection. Must be given an already-opened connection to succeed.
|
||||
@@ -68,7 +68,7 @@ declare module 'vscode-mssql' {
|
||||
/**
|
||||
* Information about a database connection
|
||||
*/
|
||||
export interface IConnectionInfo {
|
||||
export interface IConnectionInfo {
|
||||
/**
|
||||
* server name
|
||||
*/
|
||||
@@ -92,12 +92,12 @@ declare module 'vscode-mssql' {
|
||||
/**
|
||||
* email
|
||||
*/
|
||||
email: string;
|
||||
email: string | undefined;
|
||||
|
||||
/**
|
||||
* accountId
|
||||
*/
|
||||
accountId: string;
|
||||
accountId: string | undefined;
|
||||
|
||||
/**
|
||||
* The port number to connect to.
|
||||
@@ -112,7 +112,7 @@ declare module 'vscode-mssql' {
|
||||
/**
|
||||
* Gets or sets the azure account token to use.
|
||||
*/
|
||||
azureAccountToken: string;
|
||||
azureAccountToken: string | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets a Boolean value that indicates whether SQL Server uses SSL encryption for all data sent between the client and server if
|
||||
@@ -123,109 +123,109 @@ declare module 'vscode-mssql' {
|
||||
/**
|
||||
* Gets or sets a value that indicates whether the channel will be encrypted while bypassing walking the certificate chain to validate trust.
|
||||
*/
|
||||
trustServerCertificate: boolean;
|
||||
trustServerCertificate: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets a Boolean value that indicates if security-sensitive information, such as the password, is not returned as part of the connection
|
||||
* if the connection is open or has ever been in an open state.
|
||||
*/
|
||||
persistSecurityInfo: boolean;
|
||||
persistSecurityInfo: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.
|
||||
*/
|
||||
connectTimeout: number;
|
||||
connectTimeout: number | undefined;
|
||||
|
||||
/**
|
||||
* The number of reconnections attempted after identifying that there was an idle connection failure.
|
||||
*/
|
||||
connectRetryCount: number;
|
||||
connectRetryCount: number | undefined;
|
||||
|
||||
/**
|
||||
* Amount of time (in seconds) between each reconnection attempt after identifying that there was an idle connection failure.
|
||||
*/
|
||||
connectRetryInterval: number;
|
||||
connectRetryInterval: number | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the name of the application associated with the connection string.
|
||||
*/
|
||||
applicationName: string;
|
||||
applicationName: string | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the name of the workstation connecting to SQL Server.
|
||||
*/
|
||||
workstationId: string;
|
||||
workstationId: string | undefined;
|
||||
|
||||
/**
|
||||
* Declares the application workload type when connecting to a database in an SQL Server Availability Group.
|
||||
*/
|
||||
applicationIntent: string;
|
||||
applicationIntent: string | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the SQL Server Language record name.
|
||||
*/
|
||||
currentLanguage: string;
|
||||
currentLanguage: string | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets a Boolean value that indicates whether the connection will be pooled or explicitly opened every time that the connection is requested.
|
||||
*/
|
||||
pooling: boolean;
|
||||
pooling: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the maximum number of connections allowed in the connection pool for this specific connection string.
|
||||
*/
|
||||
maxPoolSize: number;
|
||||
maxPoolSize: number | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the minimum number of connections allowed in the connection pool for this specific connection string.
|
||||
*/
|
||||
minPoolSize: number;
|
||||
minPoolSize: number | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the minimum time, in seconds, for the connection to live in the connection pool before being destroyed.
|
||||
*/
|
||||
loadBalanceTimeout: number;
|
||||
loadBalanceTimeout: number | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets a Boolean value that indicates whether replication is supported using the connection.
|
||||
*/
|
||||
replication: boolean;
|
||||
replication: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets a string that contains the name of the primary data file. This includes the full path name of an attachable database.
|
||||
*/
|
||||
attachDbFilename: string;
|
||||
attachDbFilename: string | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the name or address of the partner server to connect to if the primary server is down.
|
||||
*/
|
||||
failoverPartner: string;
|
||||
failoverPartner: string | undefined;
|
||||
|
||||
/**
|
||||
* If your application is connecting to an AlwaysOn availability group (AG) on different subnets, setting MultiSubnetFailover=true
|
||||
* provides faster detection of and connection to the (currently) active server.
|
||||
*/
|
||||
multiSubnetFailover: boolean;
|
||||
multiSubnetFailover: boolean | undefined;
|
||||
|
||||
/**
|
||||
* When true, an application can maintain multiple active result sets (MARS).
|
||||
*/
|
||||
multipleActiveResultSets: boolean;
|
||||
multipleActiveResultSets: boolean | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the size in bytes of the network packets used to communicate with an instance of SQL Server.
|
||||
*/
|
||||
packetSize: number;
|
||||
packetSize: number | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets a string value that indicates the type system the application expects.
|
||||
*/
|
||||
typeSystemVersion: string;
|
||||
typeSystemVersion: string | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the connection string to use for this connection.
|
||||
*/
|
||||
connectionString: string;
|
||||
connectionString: string | undefined;
|
||||
}
|
||||
|
||||
export const enum ExtractTarget {
|
||||
|
||||
Reference in New Issue
Block a user