From 423cf8d3d541330609e8558542f9ec2b5c7bd3a4 Mon Sep 17 00:00:00 2001 From: Leila Lali Date: Tue, 14 Sep 2021 13:49:57 -0700 Subject: [PATCH] SQL Proj - Added an option to deploy to docker to select the base image (#17067) * Added an option to deploy to docker to select the base image --- .../sql-database-projects/src/common/constants.ts | 3 ++- .../src/dialogs/deployDatabaseQuickpick.ts | 15 +++++++++++++++ .../src/models/deploy/deployProfile.ts | 7 +++++++ .../src/models/deploy/deployService.ts | 15 +++++++++------ .../src/test/deploy/deployService.test.ts | 13 +++++++++---- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index e6533b269d..b11ab9ad7b 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -135,12 +135,13 @@ export const enterPortNumber = localize('enterPortNumber', "Enter port number or 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"); +export const enterBaseImage = localize('enterBaseImage', "Enter the base SQL Server docker image or press enter to use the default value"); 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 defaultDockerBaseImage = 'mcr.microsoft.com/mssql/server:2019-latest'; export const commandsFolderName = 'commands'; export const mssqlFolderName = '.mssql'; export const dockerFileName = 'Dockerfile'; diff --git a/extensions/sql-database-projects/src/dialogs/deployDatabaseQuickpick.ts b/extensions/sql-database-projects/src/dialogs/deployDatabaseQuickpick.ts index 8357bbb7a3..0e50748d7b 100644 --- a/extensions/sql-database-projects/src/dialogs/deployDatabaseQuickpick.ts +++ b/extensions/sql-database-projects/src/dialogs/deployDatabaseQuickpick.ts @@ -104,12 +104,27 @@ export async function launchPublishToDockerContainerQuickpick(project: Project): return undefined; } + let baseImage: string | undefined = ''; + baseImage = await vscode.window.showInputBox({ + title: constants.enterBaseImage, + ignoreFocusOut: true, + value: constants.defaultDockerBaseImage, + validateInput: input => utils.isEmptyString(input) ? constants.valueCannotBeEmpty : undefined + } + ); + + // Return when user hits escape + if (!baseImage) { + return undefined; + } + localDbSetting = { serverName: 'localhost', userName: 'sa', dbName: project.projectFileName, password: password, port: +portNumber, + dockerBaseImage: baseImage }; let deploySettings = await getPublishDatabaseSettings(project, false); diff --git a/extensions/sql-database-projects/src/models/deploy/deployProfile.ts b/extensions/sql-database-projects/src/models/deploy/deployProfile.ts index 5c1d1b9016..f9bc7ea1f7 100644 --- a/extensions/sql-database-projects/src/models/deploy/deployProfile.ts +++ b/extensions/sql-database-projects/src/models/deploy/deployProfile.ts @@ -1,3 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + import { IDeploySettings } from '../IDeploySettings'; export enum AppSettingType { @@ -21,4 +26,6 @@ export interface ILocalDbSetting { userName: string, password: string, dbName: string, + dockerBaseImage: string, + connectionRetryTimeout?: number } diff --git a/extensions/sql-database-projects/src/models/deploy/deployService.ts b/extensions/sql-database-projects/src/models/deploy/deployService.ts index 0b467392e5..71fbf4bf88 100644 --- a/extensions/sql-database-projects/src/models/deploy/deployService.ts +++ b/extensions/sql-database-projects/src/models/deploy/deployService.ts @@ -20,6 +20,9 @@ export class DeployService { constructor(private _outputChannel: vscode.OutputChannel) { } + private DefaultSqlRetryTimeoutInSec: number = 10; + private DefaultSqlNumberOfRetries: number = 10; + private createConnectionStringTemplate(runtime: string | undefined): string { switch (runtime?.toLocaleLowerCase()) { case 'dotnet': @@ -106,7 +109,7 @@ export class DeployService { // Create commands // - await this.createCommands(mssqlFolderPath, commandsFolderPath, dockerFilePath, startFilePath, imageLabel); + await this.createCommands(mssqlFolderPath, commandsFolderPath, dockerFilePath, startFilePath, imageLabel, profile.localDbSetting.dockerBaseImage); this.logToOutput(constants.runningDockerMessage); // Building the image and running the docker @@ -144,7 +147,7 @@ export class DeployService { private async buildAndRunDockerContainer(dockerFilePath: string, imageName: string, root: string, profile: ILocalDbSetting, imageLabel: string): Promise { this.logToOutput('Building docker image ...'); - await utils.executeCommand(`docker pull ${constants.dockerBaseImage}`, this._outputChannel); + await utils.executeCommand(`docker pull ${profile.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); @@ -254,7 +257,7 @@ export class DeployService { return connectionResult ? connectionResult.connectionId : connection; } - public async getConnection(profile: ILocalDbSetting, savePassword: boolean, database: string, timeoutInSeconds: number = 5): Promise { + public async getConnection(profile: ILocalDbSetting, savePassword: boolean, database: string): Promise { const getAzdataApi = await utils.getAzdataApi(); let connection = await utils.retry( constants.connectingToSqlServerOnDockerMessage, @@ -264,7 +267,7 @@ export class DeployService { this.validateConnection, this.formatConnectionResult, this._outputChannel, - 5, timeoutInSeconds); + this.DefaultSqlNumberOfRetries, profile.connectionRetryTimeout || this.DefaultSqlRetryTimeoutInSec); if (connection) { const connectionResult = connection; @@ -311,7 +314,7 @@ export class DeployService { } // Creates command file and docker file needed for deploy operation - private async createCommands(mssqlFolderPath: string, commandsFolderPath: string, dockerFilePath: string, startFilePath: string, imageLabel: string): Promise { + private async createCommands(mssqlFolderPath: string, commandsFolderPath: string, dockerFilePath: string, startFilePath: string, imageLabel: string, baseImage: string): Promise { // Create mssql folders if doesn't exist // await utils.createFolderIfNotExist(mssqlFolderPath); @@ -328,7 +331,7 @@ export class DeployService { // await this.createFile(dockerFilePath, ` -FROM ${constants.dockerBaseImage} +FROM ${baseImage} ENV ACCEPT_EULA=Y ENV MSSQL_PID=Developer LABEL ${imageLabel} 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 1496a13e8e..e20dae66b4 100644 --- a/extensions/sql-database-projects/src/test/deploy/deployService.test.ts +++ b/extensions/sql-database-projects/src/test/deploy/deployService.test.ts @@ -71,7 +71,9 @@ describe('deploy service', function (): void { password: 'PLACEHOLDER', port: 1433, serverName: 'localhost', - userName: 'sa' + userName: 'sa', + dockerBaseImage: 'image', + connectionRetryTimeout: 1 } }; const projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline); @@ -93,7 +95,9 @@ describe('deploy service', function (): void { password: 'PLACEHOLDER', port: 1433, serverName: 'localhost', - userName: 'sa' + userName: 'sa', + dockerBaseImage: 'image', + connectionRetryTimeout: 1 }; const deployService = new DeployService(testContext.outputChannel); @@ -103,7 +107,7 @@ describe('deploy service', function (): void { 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); + let connection = await deployService.getConnection(localDbSettings, false, 'master'); should(connection).equals('connection'); }); @@ -138,7 +142,8 @@ describe('deploy service', function (): void { password: 'PLACEHOLDER', port: 1433, serverName: 'localhost', - userName: 'sa' + userName: 'sa', + dockerBaseImage: 'image' } };