diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index 1c94afe9bb..2e55c4c69f 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -160,7 +160,8 @@ export const enterConnStringTemplateDescription = localize('enterConnStringTempl 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 publishProjectSucceed = localize('publishProjectSucceed', "Database project published successfully"); +export const publishingProjectMessage = localize('publishingProjectMessage', "Publishing project in a container..."); 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 ..."); @@ -169,8 +170,10 @@ export const dockerContainerNotRunningErrorMessage = localize('dockerContainerNo 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 const containerAlreadyExistForProject = localize('containerAlreadyExistForProject', "Other servers on container already exist for the project. Do you want to delete them?'"); +export const checkoutOutputMessage = localize('checkoutOutputMessage', "Check output pane for more details."); export function taskFailedError(taskName: string, err: string): string { return localize('taskFailedError.error', "Failed to complete task '{0}'. Error: {1}", taskName, err); } -export function publishToContainerFailed(errorMessage: string) { return localize('publishToContainerFailed', "Failed to publish to container. Check output pane for more details. {0}", errorMessage); } +export function publishToContainerFailed(errorMessage: string) { return localize('publishToContainerFailed', "Failed to publish to container. {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); } diff --git a/extensions/sql-database-projects/src/common/utils.ts b/extensions/sql-database-projects/src/common/utils.ts index cf11358727..4a028a6a59 100644 --- a/extensions/sql-database-projects/src/common/utils.ts +++ b/extensions/sql-database-projects/src/common/utils.ts @@ -437,6 +437,12 @@ export async function executeCommand(cmd: string, outputChannel: vscode.OutputCh timeout: timeout }, (err, stdout) => { if (err) { + + // removing sensitive data from the exception + sensitiveData.forEach(element => { + err.cmd = err.cmd?.replace(element, '***'); + err.message = err.message?.replace(element, '***'); + }); reject(err); } else { resolve(stdout); @@ -552,3 +558,18 @@ export function isValidSQLPassword(password: string, userName: string = 'sa'): b const hasNonAlphas = /\W/.test(password) ? 1 : 0; return !containsUserName && password.length >= 8 && password.length <= 128 && (hasUpperCase + hasLowerCase + hasNumbers + hasNonAlphas >= 3); } + +export async function showErrorMessageWithOutputChannel(errorMessageFunc: (error: string) => string, error: any, outputChannel: vscode.OutputChannel): Promise { + const result = await vscode.window.showErrorMessage(errorMessageFunc(getErrorMessage(error)), constants.checkoutOutputMessage); + if (result !== undefined) { + outputChannel.show(); + } +} + +export async function showInfoMessageWithOutputChannel(message: string, outputChannel: vscode.OutputChannel): Promise { + const result = await vscode.window.showInformationMessage(message, constants.checkoutOutputMessage); + if (result !== undefined) { + outputChannel.show(); + } +} + diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index dd87ed2a23..cfc4803585 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -73,11 +73,11 @@ export class ProjectsController { projFileWatchers = new Map(); - constructor(outputChannel: vscode.OutputChannel) { - this.netCoreTool = new NetCoreTool(outputChannel); + constructor(private _outputChannel: vscode.OutputChannel) { + this.netCoreTool = new NetCoreTool(this._outputChannel); this.buildHelper = new BuildHelper(); - this.deployService = new DeployService(outputChannel); - this.autorestHelper = new AutorestHelper(outputChannel); + this.deployService = new DeployService(this._outputChannel); + this.autorestHelper = new AutorestHelper(this._outputChannel); } public getDashboardPublishData(projectFile: string): (string | dataworkspace.IconCellValue)[][] { @@ -270,6 +270,7 @@ export class ProjectsController { if (deployProfile && deployProfile.deploySettings) { let connectionUri: string | undefined; if (deployProfile.localDbSetting) { + void utils.showInfoMessageWithOutputChannel(constants.publishingProjectMessage, this._outputChannel); connectionUri = await this.deployService.deploy(deployProfile, project); if (connectionUri) { deployProfile.deploySettings.connectionUri = connectionUri; @@ -281,16 +282,16 @@ export class ProjectsController { if (deployProfile.localDbSetting) { await this.deployService.getConnection(deployProfile.localDbSetting, true, deployProfile.localDbSetting.dbName); } - void vscode.window.showInformationMessage(constants.deployProjectSucceed); + void vscode.window.showInformationMessage(constants.publishProjectSucceed); } else { - void vscode.window.showErrorMessage(constants.publishToContainerFailed(publishResult?.errorMessage || '')); + void utils.showErrorMessageWithOutputChannel(constants.publishToContainerFailed, publishResult?.errorMessage || '', this._outputChannel); } } else { - void vscode.window.showErrorMessage(constants.publishToContainerFailed(constants.deployProjectFailedMessage)); + void utils.showErrorMessageWithOutputChannel(constants.publishToContainerFailed, constants.deployProjectFailedMessage, this._outputChannel); } } } catch (error) { - void vscode.window.showErrorMessage(constants.publishToContainerFailed(utils.getErrorMessage(error))); + void utils.showErrorMessageWithOutputChannel(constants.publishToContainerFailed, error, this._outputChannel); } return; } diff --git a/extensions/sql-database-projects/src/models/deploy/deployService.ts b/extensions/sql-database-projects/src/models/deploy/deployService.ts index 47e4c2c0bb..4690502451 100644 --- a/extensions/sql-database-projects/src/models/deploy/deployService.ts +++ b/extensions/sql-database-projects/src/models/deploy/deployService.ts @@ -111,11 +111,15 @@ export class DeployService { 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 `]); + const containerIds = await this.getCurrentDockerContainer(imageLabel); + if (containerIds && containerIds.length > 0) { + const result = await vscode.window.showWarningMessage(constants.containerAlreadyExistForProject, constants.yesString, constants.noString); + if (result === constants.yesString) { + this.logToOutput(constants.cleaningDockerImagesMessage); + await this.cleanDockerObjects(containerIds, ['docker stop', 'docker rm']); + } + } this.logToOutput(constants.creatingDeploymentSettingsMessage); // Create commands @@ -363,17 +367,22 @@ RUN ["/bin/bash", "/opt/commands/start.sh"] await fse.writeFile(filePath, content); } - public async cleanDockerObjects(commandToGetObjects: string, commandsToClean: string[]): Promise { - 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); - } + private async getCurrentIds(commandToRun: string): Promise { + const currentIds = await utils.executeCommand(commandToRun, this._outputChannel); + return currentIds ? currentIds.split(/\r?\n/) : []; + } + + public async getCurrentDockerContainer(imageLabel: string): Promise { + return await this.getCurrentIds(`docker ps -q -a --filter label=${imageLabel}`); + } + + public async cleanDockerObjects(ids: string[], commandsToClean: string[]): Promise { + 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); } } } diff --git a/extensions/sql-database-projects/src/test/deploy/deployService.test.ts b/extensions/sql-database-projects/src/test/deploy/deployService.test.ts index e0ebf4afeb..8a03afd6f9 100644 --- a/extensions/sql-database-projects/src/test/deploy/deployService.test.ts +++ b/extensions/sql-database-projects/src/test/deploy/deployService.test.ts @@ -15,6 +15,7 @@ import * as childProcess from 'child_process'; import { AppSettingType, IDeployProfile } from '../../models/deploy/deployProfile'; let fse = require('fs-extra'); let path = require('path'); +import * as constants from '../../common/constants'; export interface TestContext { outputChannel: vscode.OutputChannel; @@ -81,6 +82,7 @@ describe('deploy service', function (): void { 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(vscode.window, 'showWarningMessage').returns(Promise.resolve(constants.yesString)); sandbox.stub(azdata.tasks, 'startBackgroundOperation').callThrough(); sandbox.stub(childProcess, 'exec').yields(undefined, 'id'); let connection = await deployService.deploy(deployProfile, project1); @@ -168,10 +170,12 @@ describe('deploy service', function (): void { } }; - const appInteg = {appSettingType: AppSettingType.AzureFunction, + const appInteg = { + appSettingType: AppSettingType.AzureFunction, appSettingFile: filePath, deploySettings: undefined, - envVariableName: 'SQLConnectionString'}; + envVariableName: 'SQLConnectionString' + }; const deployService = new DeployService(testContext.outputChannel); sandbox.stub(childProcess, 'exec').yields(undefined, 'id'); @@ -240,8 +244,8 @@ describe('deploy service', function (): void { id2 id3`); - await deployService.cleanDockerObjects(`docker ps -q -a --filter label=test`, ['docker stop', 'docker rm']); + const ids = await deployService.getCurrentDockerContainer('label'); + await deployService.cleanDockerObjects(ids, ['docker stop', 'docker rm']); should(process.calledThrice); }); }); -