New UI for deploying SQL project to a new Azure server (#18833)

This commit is contained in:
Leila Lali
2022-04-29 15:39:21 -07:00
committed by GitHub
parent 14a63977c8
commit d95aff1d3c
17 changed files with 1453 additions and 367 deletions

View File

@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as utils from '../../common/utils';
import { IAccount, IAzureAccountSession, azure } from 'vscode-mssql';
/**
* Client module to call Azure APIs for getting or creating resources
*/
export class AzureSqlClient {
constructor(
private _azureAccountServiceFactory: utils.AzureAccountServiceFactory = utils.defaultAzureAccountServiceFactory,
private _azureResourceServiceFactory: utils.AzureResourceServiceFactory = utils.defaultAzureResourceServiceFactory
) { }
/**
* Returns existing Azure accounts
*/
public async getAccounts(): Promise<IAccount[]> {
const azureAccountService = await this._azureAccountServiceFactory();
return await azureAccountService.getAccounts();
}
/**
* Prompt user to login to Azure and returns the account
* @returns Azure account that user logged in to
*/
public async getAccount(): Promise<IAccount> {
const azureAccountService = await this._azureAccountServiceFactory();
return await azureAccountService.addAccount();
}
/**
* Returns Azure locations for given subscription
*/
public async getLocations(session: IAzureAccountSession): Promise<azure.subscription.Location[]> {
const azureResourceService = await this._azureResourceServiceFactory();
return await azureResourceService.getLocations(session);
}
/**
* Returns Azure sessions with subscription, tenant and token for given account
*/
public async getSessions(account: IAccount): Promise<IAzureAccountSession[]> {
const azureAccountService = await this._azureAccountServiceFactory();
return await azureAccountService.getAccountSessions(account);
}
/**
* Creates a new Azure SQL server for given subscription, resource group and location
*/
public async createOrUpdateServer(session: IAzureAccountSession, resourceGroupName: string, serverName: string, parameters: azure.sql.Server): Promise<string | undefined> {
const azureResourceService = await this._azureResourceServiceFactory();
return await azureResourceService.createOrUpdateServer(session, resourceGroupName, serverName, parameters);
}
/**
* Returns Azure resource groups for given subscription
*/
public async getResourceGroups(session: IAzureAccountSession): Promise<Array<azure.resources.ResourceGroup> | []> {
const azureResourceService = await this._azureResourceServiceFactory();
return await azureResourceService.getResourceGroups(session);
}
}

View File

@@ -5,32 +5,49 @@
import { IDeploySettings } from '../IDeploySettings';
import type * as azdataType from 'azdata';
import { IAzureAccountSession } from 'vscode-mssql';
export enum AppSettingType {
None,
AzureFunction
}
export interface IDeployProfile {
export interface ILocalDbDeployProfile {
localDbSetting?: ILocalDbSetting;
deploySettings?: IDeploySettings;
}
export interface ISqlDbDeployProfile {
sqlDbSetting?: ISqlDbSetting;
deploySettings?: IDeploySettings;
}
export interface IDeployAppIntegrationProfile {
envVariableName?: string;
appSettingFile?: string;
appSettingType: AppSettingType;
}
export interface ILocalDbSetting {
serverName: string,
port: number,
userName: string,
password: string,
dbName: string,
export interface ISqlDbSetting extends ISqlConnectionProperties {
session: IAzureAccountSession
resourceGroupName: string,
location: string
}
export interface ILocalDbSetting extends ISqlConnectionProperties {
dockerBaseImage: string,
dockerBaseImageEula: string,
connectionRetryTimeout?: number,
profileName?: string
}
export interface ISqlConnectionProperties {
tenantId?: string,
accountId?: string
serverName: string,
userName: string,
password: string,
port: number,
dbName: string,
profileName?: string,
connectionRetryTimeout?: number
}
export interface DockerImageInfo {
@@ -41,3 +58,4 @@ export interface DockerImageInfo {
export interface AgreementInfo {
link: azdataType.LinkArea;
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AppSettingType, IDeployAppIntegrationProfile, IDeployProfile, ILocalDbSetting } from './deployProfile';
import { AppSettingType, IDeployAppIntegrationProfile, ILocalDbDeployProfile, ILocalDbSetting, ISqlConnectionProperties, ISqlDbDeployProfile } from './deployProfile';
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import { Project } from '../project';
import * as constants from '../../common/constants';
@@ -13,6 +13,8 @@ import * as vscode from 'vscode';
import { ConnectionResult } from 'azdata';
import * as templates from '../../templates/templates';
import { ShellExecutionHelper } from '../../tools/shellExecutionHelper';
import { AzureSqlClient } from './azureSqlClient';
import { IFireWallRuleError } from 'vscode-mssql';
interface DockerImageSpec {
label: string;
@@ -21,7 +23,7 @@ interface DockerImageSpec {
}
export class DeployService {
constructor(private _outputChannel: vscode.OutputChannel, shellExecutionHelper: ShellExecutionHelper | undefined = undefined) {
constructor(private _azureSqlClient = new AzureSqlClient(), private _outputChannel: vscode.OutputChannel, shellExecutionHelper: ShellExecutionHelper | undefined = undefined) {
this._shellExecutionHelper = shellExecutionHelper ?? new ShellExecutionHelper(this._outputChannel);
}
@@ -50,7 +52,7 @@ export class DeployService {
return undefined;
}
public async updateAppSettings(profile: IDeployAppIntegrationProfile, deployProfile: IDeployProfile | undefined): Promise<void> {
public async updateAppSettings(profile: IDeployAppIntegrationProfile, deployProfile: ILocalDbDeployProfile | undefined): Promise<void> {
// Update app settings
//
if (!profile.appSettingFile) {
@@ -122,7 +124,37 @@ export class DeployService {
return { label: imageLabel, tag: imageTag, containerName: dockerName };
}
public async deploy(profile: IDeployProfile, project: Project): Promise<string | undefined> {
/**
* Creates a new Azure Sql server and tries to connect to the new server. If connection fails because of firewall rule, it prompts user to add firewall rule settings
* @param profile Azure Sql server settings
* @returns connection url for the new server
*/
public async createNewAzureSqlServer(profile: ISqlDbDeployProfile | undefined): Promise<string | undefined> {
if (!profile?.sqlDbSetting) {
return undefined;
}
this.logToOutput(constants.creatingAzureSqlServer(profile?.sqlDbSetting?.serverName));
// Create the server
const server = await this._azureSqlClient.createOrUpdateServer(profile.sqlDbSetting.session, profile?.sqlDbSetting.resourceGroupName, profile?.sqlDbSetting.serverName, {
location: profile?.sqlDbSetting?.location,
administratorLogin: profile?.sqlDbSetting.userName,
administratorLoginPassword: profile?.sqlDbSetting.password
});
if (server) {
this._outputChannel.appendLine(constants.serverCreated);
profile.sqlDbSetting.serverName = server;
this.logToOutput(constants.azureSqlServerCreated(profile?.sqlDbSetting?.serverName));
// Connect to the server
return await this.getConnection(profile.sqlDbSetting, false, constants.master);
}
return undefined;
}
public async deployToContainer(profile: ILocalDbDeployProfile, project: Project): Promise<string | undefined> {
return await this.executeTask(constants.deployDbTaskName, async () => {
if (!profile.localDbSetting) {
return undefined;
@@ -218,7 +250,7 @@ export class DeployService {
}
// Connects to a database
private async connectToDatabase(profile: ILocalDbSetting, saveConnectionAndPassword: boolean, database: string): Promise<ConnectionResult | string | undefined> {
private async connectToDatabase(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<ConnectionResult | string | undefined> {
const getAzdataApi = await utils.getAzdataApi();
const vscodeMssqlApi = getAzdataApi ? undefined : await utils.getVscodeMssqlApi();
if (getAzdataApi) {
@@ -248,7 +280,7 @@ export class DeployService {
encrypt: false,
connectTimeout: 30,
applicationName: 'SQL Database Project',
accountId: undefined,
accountId: profile.accountId,
azureAccountToken: undefined,
applicationIntent: undefined,
attachDbFilename: undefined,
@@ -272,9 +304,19 @@ export class DeployService {
workstationId: undefined,
profileName: profile.profileName,
expiresOn: undefined,
tenantId: undefined
tenantId: profile.tenantId
};
let connectionUrl = await vscodeMssqlApi.connect(connectionProfile, saveConnectionAndPassword);
let connectionUrl = '';
try {
connectionUrl = await vscodeMssqlApi.connect(connectionProfile, saveConnectionAndPassword);
} catch (err) {
const firewallRuleError = <IFireWallRuleError>err;
if (firewallRuleError?.connectionUri) {
await vscodeMssqlApi.promptForFirewallRule(err.connectionUri, connectionProfile);
} else {
throw err;
}
}
return connectionUrl;
} else {
return undefined;
@@ -307,7 +349,7 @@ export class DeployService {
return connectionResult ? connectionResult.connectionId : <string>connection;
}
public async getConnection(profile: ILocalDbSetting, saveConnectionAndPassword: boolean, database: string): Promise<string | undefined> {
public async getConnection(profile: ISqlConnectionProperties, saveConnectionAndPassword: boolean, database: string): Promise<string | undefined> {
const getAzdataApi = await utils.getAzdataApi();
let connection = await utils.retry(
constants.connectingToSqlServerMessage,