SQL VM deployments (#12144)

* Added sql vm deployment option

* Added more fields for sql vm deployments

* created basic sqlvm deployment. Mostly hardcoded

* added string to package.nls

* added poc deployments for sql vm

* Made some changes in the notebook that was mentioned in PR

* Added scaffolding for azure sql vm wizard.

* code cleanups

* added some async logic

* added loading component

* fixed loader code

* completed page2 of wizard

* added some more required fields.

* added some more fields

* added network settings page

* added sql server settings page

* added azure signin support and sql server settings page

* added some helper methods in wizard code

* added some fixes

* fixed azure and vm setting page
added validation in azure setting page

* added changes for the notebook variable

* validations and other bug fixes

* commenting sql storage optimization dropdown

* cleanedup wizard base page

* reversing  vm image list to display newer images first

* cleaning model code

* added validations for network setting

* Completed summary page
fixed the code poisition
some additional field validations

* fixed networking page

* - fixed an error with vm size model variable
- removed byol images because it was not working with az sql vm
- Fixed vm size display names in dropdown

* added double quotes to some localized strings

* added some space inside strings

* -Added live validations
-Restyled network component
-Added required to regions
-Some bug fixes

* -redesigned summary page
-localized some strings

* Fixed summary page section titles

* -Fixed validations on sql server settings page
-Fixed some fields on Summary Page

* corrected onleave validation
using array for error messages
using Promises.all

* Fixed bug on network settings dropdowns when user does not have existing resource to populate them

* Change resource deployment display name
Added Ninar's iteration of the notebook
Changed RDP check box label
Surfacing API errors to user
Filtering regions based on Azure VM regions and user's subscription region
Made form validation async
Displaying new checkbox on network page when dropdowns empty
Fixed a small bug in SQL auth form validation
Made summary single item per row and fixed the gaps in spacing
Fixed validations in vm page
Checking if vm name already exists on azure

* Fixed sql vm eula
Fixed sql vm description
Added hyperlink for more info on vm sizes

* Replaced loading component with dropdown loaders.

* localized string
Fixed a bug in network settings page

* Added additonal filtering

* added reverse to image images

* Fixing some merge related issues
This commit is contained in:
Aasim Khan
2020-09-16 14:02:03 -07:00
committed by GitHub
parent 58252bcf97
commit f62020e1ec
15 changed files with 2748 additions and 3 deletions

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export const standardWidth: string = '480px';
// Deploy Azure SQL VM wizard constants
export const WizardTitle = localize('deployAzureSQLVM.NewSQLVMTitle', "Deploy Azure SQL VM");
export const WizardDoneButtonLabel = localize('deployAzureSQLVM.ScriptToNotebook', "Script to Notebook");
export const MissingRequiredInformationErrorMessage = localize('deployAzureSQLVM.MissingRequiredInfoError', "Please fill out the required fields marked with red asterisks.");
// Azure settings page constants
export const AzureSettingsPageTitle = localize('deployAzureSQLVM.AzureSettingsPageTitle', "Azure settings");
export const AzureAccountDropdownLabel = localize('deployAzureSQLVM.AzureAccountDropdownLabel', "Azure Account");
export const AzureAccountSubscriptionDropdownLabel = localize('deployAzureSQLVM.AzureSubscriptionDropdownLabel', "Subscription");
export const AzureAccountResourceGroupDropdownLabel = localize('deployAzureSQLVM.ResourceGroup', "Resource Group");
export const AzureAccountRegionDropdownLabel = localize('deployAzureSQLVM.AzureRegionDropdownLabel', "Region");
// VM settings page constants
export const VmSettingsPageTitle = localize('deployeAzureSQLVM.VmSettingsPageTitle', "Virtual machine settings");
export const VmNameTextBoxLabel = localize('deployAzureSQLVM.VmNameTextBoxLabel', "Virtual machine name");
export const VmAdminUsernameTextBoxLabel = localize('deployAzureSQLVM.VmAdminUsernameTextBoxLabel', "Administrator account username");
export const VmAdminPasswordTextBoxLabel = localize('deployAzureSQLVM.VmAdminPasswordTextBoxLabel', "Administrator account password");
export const VmAdminConfirmPasswordTextBoxLabel = localize('deployAzureSQLVM.VmAdminConfirmPasswordTextBoxLabel', "Confirm password");
export const VmImageDropdownLabel = localize('deployAzureSQLVM.VmImageDropdownLabel', "Image");
export const VmSkuDropdownLabel = localize('deployAzureSQLVM.VmSkuDropdownLabel', "Image SKU");
export const VmVersionDropdownLabel = localize('deployAzureSQLVM.VmImageVersionDropdownLabel', "Image Version");
export const VmSizeDropdownLabel = localize('deployAzureSQLVM.VmSizeDropdownLabel', "Size");
export const VmSizeLearnMoreLabel = localize('deployeAzureSQLVM.VmSizeLearnMoreLabel', "Click here to learn more about pricing and supported VM sizes");
// Network settings page constants
export const NetworkSettingsPageTitle = localize('deployAzureSQLVM.NetworkSettingsPageTitle', "Networking");
export const NetworkSettingsPageDescription = localize('deployAzureSQLVM.NetworkSettingsPageDescription', "Configure network settings");
export const NetworkSettingsNewVirtualNetwork = localize('deployAzureSQLVM.NetworkSettingsNewVirtualNetwork', "New virtual network");
export const VirtualNetworkDropdownLabel = localize('deployAzureSQLVM.VirtualNetworkDropdownLabel', "Virtual Network");
export const NetworkSettingsNewSubnet = localize('deployAzureSQLVM.NetworkSettingsNewSubnet', "New subnet");
export const SubnetDropdownLabel = localize('deployAzureSQLVM.SubnetDropdownLabel', "Subnet");
export const PublicIPDropdownLabel = localize('deployAzureSQLVM.PublicIPDropdownLabel', "Public IP");
export const NetworkSettingsNewPublicIp = localize('deployAzureSQLVM.NetworkSettingsUseExistingPublicIp', "New public ip");
export const RDPAllowCheckboxLabel = localize('deployAzureSQLVM.VmRDPAllowCheckboxLabel', "Enable Remote Desktop (RDP) inbound port (3389)");
// SQL Server settings page constants
export const SqlServerSettingsPageTitle = localize('deployAzureSQLVM.SqlServerSettingsPageTitle', "SQL Servers settings");
export const SqlConnectivityTypeDropdownLabel = localize('deployAzureSQLVM.SqlConnectivityTypeDropdownLabel', "SQL connectivity");
export const SqlPortLabel = localize('deployAzureSQLVM.SqlPortLabel', "Port");
export const SqlEnableSQLAuthenticationLabel = localize('deployAzureSQLVM.SqlEnableSQLAuthenticationLabel', "Enable SQL authentication");
export const SqlAuthenticationUsernameLabel = localize('deployAzureSQLVM.SqlAuthenticationUsernameLabel', "Username");
export const SqlAuthenticationPasswordLabel = localize('deployAzureSQLVM.SqlAuthenticationPasswordLabel', "Password");
export const SqlAuthenticationConfirmPasswordLabel = localize('deployAzureSQLVM.SqlAuthenticationConfirmPasswordLabel', "Confirm password");

View File

@@ -0,0 +1,223 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as constants from './constants';
import { INotebookService } from '../../services/notebookService';
import { IToolsService } from '../../services/toolsService';
import { WizardBase } from '../wizardBase';
import { WizardPageBase } from '../wizardPageBase';
import { DeployAzureSQLVMWizardModel } from './deployAzureSQLVMWizardModel';
import { AzureSQLVMWizardInfo } from '../../interfaces';
import { AzureSettingsPage } from './pages/azureSettingsPage';
import { VmSettingsPage } from './pages/vmSettingsPage';
import axios, { AxiosRequestConfig } from 'axios';
import { NetworkSettingsPage } from './pages/networkSettingsPage';
import { SqlServerSettingsPage } from './pages/sqlServerSettingsPage';
import { AzureSQLVMSummaryPage } from './pages/summaryPage';
import { EOL } from 'os';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class DeployAzureSQLVMWizard extends WizardBase<DeployAzureSQLVMWizard, WizardPageBase<DeployAzureSQLVMWizard>, DeployAzureSQLVMWizardModel> {
private cache: Map<string, any> = new Map();
constructor(private wizardInfo: AzureSQLVMWizardInfo, private _notebookService: INotebookService, private _toolsService: IToolsService) {
super(
constants.WizardTitle,
new DeployAzureSQLVMWizardModel(),
_toolsService
);
}
protected initialize(): void {
this.setPages(this.getPages());
this.wizardObject.generateScriptButton.hidden = true;
this.wizardObject.doneButton.label = constants.WizardDoneButtonLabel;
}
public get notebookService(): INotebookService {
return this._notebookService;
}
public get toolService(): IToolsService {
return this._toolsService;
}
protected async onOk(): Promise<void> {
await this.scriptToNotebook();
}
protected onCancel(): void {
}
private getPages(): WizardPageBase<DeployAzureSQLVMWizard>[] {
const pages: WizardPageBase<DeployAzureSQLVMWizard>[] = [];
pages.push(new AzureSettingsPage(this));
pages.push(new VmSettingsPage(this));
pages.push(new NetworkSettingsPage(this));
pages.push(new SqlServerSettingsPage(this));
pages.push(new AzureSQLVMSummaryPage(this));
return pages;
}
private async scriptToNotebook(): Promise<void> {
this.setEnvironmentVariables(process.env);
const variableValueStatements = this.model.getCodeCellContentForNotebook();
const insertionPosition = 2; // Cell number 5 is the position where the python variable setting statements need to be inserted in this.wizardInfo.notebook.
try {
await this.notebookService.launchNotebookWithEdits(this.wizardInfo.notebook, variableValueStatements, insertionPosition);
} catch (error) {
vscode.window.showErrorMessage(error);
}
}
private setEnvironmentVariables(env: NodeJS.ProcessEnv): void {
env['AZDATA_NB_VAR_AZURE_SQLVM_PASSWORD'] = this.model.vmPassword;
env['AZDATA_NB_VAR_AZURE_SQLVM_SQL_PASSWORD'] = this.model.sqlAuthenticationPassword;
}
public async getRequest(url: string, useCache = false): Promise<any> {
if (useCache) {
if (this.cache.has(url)) {
return this.cache.get(url);
}
}
let token = this.model.securityToken.token;
const config: AxiosRequestConfig = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
validateStatus: () => true // Never throw
};
const response = await axios.get(url, config);
if (response.status !== 200) {
let errorMessage: string[] = [];
errorMessage.push(response.status.toString());
errorMessage.push(response.statusText);
if (response.data && response.data.error) {
errorMessage.push(`${response.data.error.code} : ${response.data.error.message}`);
}
vscode.window.showErrorMessage(errorMessage.join(EOL));
}
if (useCache) {
this.cache.set(url, response);
}
return response;
}
public createFormRowComponent(view: azdata.ModelView, title: string, description: string, component: azdata.Component, required: boolean): azdata.FlexContainer {
component.updateProperties({
required: required,
width: '480px'
});
const labelText = view.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>(
{
value: title,
width: '250px',
description: description,
requiredIndicator: required,
})
.component();
labelText.updateCssStyles({
'font-weight': '400',
'font-size': '13px',
});
const flexContainer = view.modelBuilder.flexContainer()
.withLayout(
{
flexFlow: 'row',
alignItems: 'center',
})
.withItems(
[labelText, component],
{
CSSStyles: { 'margin-right': '5px' }
})
.component();
return flexContainer;
}
public changeComponentDisplay(component: azdata.Component, display: ('none' | 'block')) {
component.updateProperties({
required: display === 'block'
});
component.updateCssStyles({
display: display
});
}
public changeRowDisplay(container: azdata.FlexContainer, display: ('none' | 'block')) {
container.items.map((component) => {
component.updateProperties({
required: (display === 'block'),
});
component.updateCssStyles({
display: display,
});
});
}
public addDropdownValues(component: azdata.DropDownComponent, values: azdata.CategoryValue[], width?: number) {
component.updateProperties({
values: values,
width: '480px'
});
}
public showErrorMessage(message: string) {
this.wizardObject.message = {
text: message,
level: azdata.window.MessageLevel.Error
};
}
public validatePassword(password: string): string[] {
/**
* 1. Password length should be between 12 and 123.
* 2. Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character.
*/
let errorMessages = [];
if (password.length < 12 || password.length > 123) {
errorMessages.push(localize('sqlVMDeploymentWizard.PasswordLengthError', "Password must be between 12 and 123 characters long."));
}
let charTypeCounter = 0;
if (new RegExp('.*[a-z].*').test(password)) {
charTypeCounter++;
}
if (new RegExp('.*[A-Z].*').test(password)) {
charTypeCounter++;
}
if (new RegExp('.*[0-9].*').test(password)) {
charTypeCounter++;
}
if (/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/.test(password)) {
charTypeCounter++;
}
if (charTypeCounter < 3) {
errorMessages.push(localize('sqlVMDeploymentWizard.PasswordSpecialCharRequirementError', "Password must have 3 of the following: 1 lower case character, 1 upper case character, 1 number, and 1 special character."));
}
return errorMessages;
}
}

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EOL } from 'os';
import * as azdata from 'azdata';
import { Model } from '../model';
export class DeployAzureSQLVMWizardModel extends Model {
public azureAccount!: azdata.Account;
public securityToken!: any;
public azureSubscription!: string;
public azureSubscriptionDisplayName!: string;
public azureResouceGroup!: string;
public azureRegion!: string;
public vmName!: string;
public vmUsername!: string;
public vmPassword!: string;
public vmImage!: string;
public vmImageSKU!: string;
public vmImageVersion!: string;
public vmSize!: string;
public virtualNetworkName!: string;
public newVirtualNetwork!: 'True' | 'False';
public subnetName!: string;
public newSubnet!: 'True' | 'False';
public publicIpName!: string;
public newPublicIp!: 'True' | 'False';
public allowRDP!: 'True' | 'False';
public sqlConnectivityType!: string;
public port!: number;
public enableSqlAuthentication!: string;
public sqlAuthenticationUsername!: string;
public sqlAuthenticationPassword!: string;
public sqlOptimizationDropdown!: string;
constructor() {
super();
}
public getCodeCellContentForNotebook(): string[] {
const statements: string[] = [];
statements.push('import os');
statements.push(`azure_sqlvm_nb_var_subscription = '${this.azureSubscription}'`);
statements.push(`azure_sqlvm_nb_var_resource_group_name = '${this.azureResouceGroup}'`);
statements.push(`azure_sqlvm_location = '${this.azureRegion}'`);
statements.push(`azure_sqlvm_vmname = '${this.vmName}'`);
statements.push(`azure_sqlvm_username = '${this.vmUsername}'`);
statements.push(`azure_sqlvm_image = '${this.vmImage}'`);
statements.push(`azure_sqlvm_image_sku = '${this.vmImageSKU}'`);
statements.push(`azure_sqlvm_image_version = '${this.vmImageVersion}'`);
statements.push(`azure_sqlvm_vmsize = '${this.vmSize}'`);
statements.push(`azure_sqlvm_newVirtualNetwork = ${this.newVirtualNetwork}`);
statements.push(`azure_sqlvm_virtnet = '${this.virtualNetworkName}'`);
statements.push(`azure_sqlvm_newSubnet = ${this.newSubnet}`);
statements.push(`azure_sqlvm_subnet = '${this.subnetName}'`);
statements.push(`azure_sqlvm_newPublicIp = ${this.newPublicIp}`);
statements.push(`azure_sqlvm_publicip = '${this.publicIpName}'`);
statements.push(`azure_sqlvm_allow_rdp = ${this.allowRDP}`);
statements.push(`azure_sqlvm_sqlConnectivityType = '${this.sqlConnectivityType}'`);
statements.push(`azure_sqlvm_port = '${this.port}'`);
statements.push(`azure_sqlvm_enableSqlAuthentication = ${this.enableSqlAuthentication}`);
statements.push(`azure_sqlvm_sqlAuthenticationUsername = '${this.sqlAuthenticationUsername}'`);
return statements.map(line => line + EOL);
}
}

View File

@@ -0,0 +1,302 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as constants from '../constants';
import { WizardPageBase } from '../../wizardPageBase';
import { DeployAzureSQLVMWizard } from '../deployAzureSQLVMWizard';
import { apiService } from '../../../services/apiService';
import { azureResource } from 'azureResource';
import * as vscode from 'vscode';
export class AzureSettingsPage extends WizardPageBase<DeployAzureSQLVMWizard> {
// <- means depends on
//dropdown for azure accounts
private _azureAccountsDropdown!: azdata.DropDownComponent;
private signInButton!: azdata.ButtonComponent;
private refreshButton!: azdata.ButtonComponent;
private buttonFlexContainer!: azdata.FlexContainer;
//dropdown for subscription accounts <- azure account dropdown
private _azureSubscriptionsDropdown!: azdata.DropDownComponent;
//dropdown for resource groups <- subscription dropdown
private _resourceGroupDropdown!: azdata.DropDownComponent;
//dropdown for azure regions <- subscription dropdown
private _azureRegionsDropdown!: azdata.DropDownComponent;
private _form!: azdata.FormContainer;
private _accountsMap!: Map<string, azdata.Account>;
private _subscriptionsMap!: Map<string, azureResource.AzureResourceSubscription>;
constructor(wizard: DeployAzureSQLVMWizard) {
super(
constants.AzureSettingsPageTitle,
'',
wizard
);
this._accountsMap = new Map();
this._subscriptionsMap = new Map();
}
public async initialize() {
this.pageObject.registerContent(async (view: azdata.ModelView) => {
await Promise.all([
this.createAzureAccountsDropdown(view),
this.createAzureSubscriptionsDropdown(view),
this.createResourceDropdown(view),
this.createAzureRegionsDropdown(view)
]);
this.populateAzureAccountsDropdown();
this._form = view.modelBuilder.formContainer()
.withFormItems(
[
{
component: this.wizard.createFormRowComponent(view, constants.AzureAccountDropdownLabel, '', this._azureAccountsDropdown, true)
},
{
component: this.buttonFlexContainer
},
{
component: this.wizard.createFormRowComponent(view, constants.AzureAccountSubscriptionDropdownLabel, '', this._azureSubscriptionsDropdown, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.AzureAccountResourceGroupDropdownLabel, '', this._resourceGroupDropdown, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.AzureAccountRegionDropdownLabel, '', this._azureRegionsDropdown, true)
}
],
{
horizontal: false,
componentWidth: '100%'
})
.withLayout({ width: '100%' })
.component();
return view.initializeModel(this._form);
});
}
public async onEnter(): Promise<void> {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
}
public async onLeave(): Promise<void> {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
}
private async createAzureAccountsDropdown(view: azdata.ModelView) {
this._azureAccountsDropdown = view.modelBuilder.dropDown().withProperties({}).component();
this._azureAccountsDropdown.onValueChanged(async (value) => {
this.wizard.model.azureAccount = this._accountsMap.get(value.selected)!;
this.populateAzureSubscriptionsDropdown();
});
this.signInButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
label: 'Sign In',
width: '100px'
}).component();
this.refreshButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
label: 'Refresh',
width: '100px'
}).component();
this.signInButton.onDidClick(async (event) => {
await vscode.commands.executeCommand('workbench.actions.modal.linkedAccount');
await this.populateAzureAccountsDropdown();
});
this.refreshButton.onDidClick(async (event) => {
await this.populateAzureAccountsDropdown();
});
this.buttonFlexContainer = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'row'
}).withItems([this.signInButton, this.refreshButton], { CSSStyles: { 'margin-right': '5px', } }).component();
}
private async populateAzureAccountsDropdown() {
this._azureAccountsDropdown.loading = true;
let accounts = await azdata.accounts.getAllAccounts();
if (accounts.length === 0) {
this.wizard.showErrorMessage('Sign in to an Azure account first');
return;
} else {
this.wizard.showErrorMessage('');
}
this.wizard.addDropdownValues(
this._azureAccountsDropdown,
accounts.map((account): azdata.CategoryValue => {
let accountCategoryValue = {
displayName: account.displayInfo.displayName,
name: account.displayInfo.displayName
};
this._accountsMap.set(accountCategoryValue.displayName, account);
return accountCategoryValue;
}),
);
this.wizard.model.azureAccount = accounts[0];
this._azureAccountsDropdown.loading = false;
await this.populateAzureSubscriptionsDropdown();
}
private async createAzureSubscriptionsDropdown(view: azdata.ModelView) {
this._azureSubscriptionsDropdown = view.modelBuilder.dropDown().withProperties({}).component();
this._azureSubscriptionsDropdown.onValueChanged(async (value) => {
let currentSubscriptionValue = this._azureSubscriptionsDropdown.value as azdata.CategoryValue;
this.wizard.model.azureSubscription = currentSubscriptionValue.name;
this.wizard.model.azureSubscriptionDisplayName = currentSubscriptionValue.displayName;
this.wizard.model.securityToken = await azdata.accounts.getAccountSecurityToken(
this.wizard.model.azureAccount,
this._subscriptionsMap.get(currentSubscriptionValue.name)?.tenant!,
azdata.AzureResource.ResourceManagement
);
this.populateResourceGroupDropdown();
this.populateAzureRegionsDropdown();
});
}
private async populateAzureSubscriptionsDropdown() {
this._azureSubscriptionsDropdown.loading = true;
let subService = await apiService.azurecoreApi;
let currentAccountDropdownValue = (this._azureAccountsDropdown.value as azdata.CategoryValue);
if (currentAccountDropdownValue === undefined) {
this._azureSubscriptionsDropdown.loading = false;
await this.populateResourceGroupDropdown();
await this.populateAzureRegionsDropdown();
return;
}
let currentAccount = this._accountsMap.get(currentAccountDropdownValue.name);
let subscriptions = (await subService.getSubscriptions(currentAccount, true)).subscriptions;
if (subscriptions === undefined || subscriptions.length === 0) {
this._azureSubscriptionsDropdown.updateProperties({
values: []
});
this._azureSubscriptionsDropdown.loading = false;
await this.populateResourceGroupDropdown();
await this.populateAzureRegionsDropdown();
return;
}
subscriptions.sort((a: any, b: any) => a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()));
this.wizard.addDropdownValues(
this._azureSubscriptionsDropdown,
subscriptions.map((subscription: any): azdata.CategoryValue => {
let subscriptionCategoryValue = {
displayName: subscription.name + ' - ' + subscription.id,
name: subscription.id
};
this._subscriptionsMap.set(subscriptionCategoryValue.name, subscription);
return subscriptionCategoryValue;
})
);
this.wizard.model.azureSubscription = (this._azureSubscriptionsDropdown.value as azdata.CategoryValue).name;
this.wizard.model.azureSubscriptionDisplayName = (this._azureSubscriptionsDropdown.value as azdata.CategoryValue).displayName;
this.wizard.model.securityToken = await azdata.accounts.getAccountSecurityToken(
this.wizard.model.azureAccount,
this._subscriptionsMap.get((this._azureSubscriptionsDropdown.value as azdata.CategoryValue).name)?.tenant!,
azdata.AzureResource.ResourceManagement
);
this._azureSubscriptionsDropdown.loading = false;
await this.populateResourceGroupDropdown();
await this.populateAzureRegionsDropdown();
}
private async createResourceDropdown(view: azdata.ModelView) {
this._resourceGroupDropdown = view.modelBuilder.dropDown().withProperties({
required: true
}).component();
this._resourceGroupDropdown.onValueChanged(async (value) => {
this.wizard.model.azureResouceGroup = value.selected;
});
}
private async populateResourceGroupDropdown() {
this._resourceGroupDropdown.loading = true;
let subService = await apiService.azurecoreApi;
let currentSubscriptionValue = this._azureSubscriptionsDropdown.value as azdata.CategoryValue;
if (currentSubscriptionValue === undefined || currentSubscriptionValue.displayName === '') {
this._resourceGroupDropdown.updateProperties({
values: []
});
this._resourceGroupDropdown.loading = false;
return;
}
let currentSubscription = this._subscriptionsMap.get(currentSubscriptionValue.name);
let resourceGroups = (await subService.getResourceGroups(this.wizard.model.azureAccount, currentSubscription, true)).resourceGroups;
if (resourceGroups === undefined || resourceGroups.length === 0) {
this._resourceGroupDropdown.loading = false;
this._resourceGroupDropdown.updateProperties({
values: []
});
return;
}
resourceGroups.sort((a: any, b: any) => a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()));
this._resourceGroupDropdown.updateProperties({
values: resourceGroups.map((resourceGroup: any) => {
return {
displayName: resourceGroup.name,
name: resourceGroup.name
};
})
});
this.wizard.model.azureResouceGroup = (this._resourceGroupDropdown.value as azdata.CategoryValue).name;
this._resourceGroupDropdown.loading = false;
}
private async createAzureRegionsDropdown(view: azdata.ModelView) {
this._azureRegionsDropdown = view.modelBuilder.dropDown().withProperties({
required: true
}).component();
this._azureRegionsDropdown.onValueChanged((value) => {
this.wizard.model.azureRegion = (this._azureRegionsDropdown.value as azdata.CategoryValue).name;
});
}
private async populateAzureRegionsDropdown() {
this._azureRegionsDropdown.loading = true;
let supportedRegions = 'eastus, eastus2, westus, centralus, northcentralus, southcentralus, northeurope, westeurope, eastasia, southeastasia, japaneast, japanwest, australiaeast, australiasoutheast, australiacentral, brazilsouth, southindia, centralindia, westindia, canadacentral, canadaeast, westus2, westcentralus, uksouth, ukwest, koreacentral, koreasouth, francecentral, southafricanorth, uaenorth, switzerlandnorth, germanywestcentral, norwayeast';
let supportedRegionsArray = supportedRegions.split(', ');
let url = `https://management.azure.com/subscriptions/${this.wizard.model.azureSubscription}/locations?api-version=2020-01-01`;
const response = await this.wizard.getRequest(url, false);
response.data.value = response.data.value.sort((a: any, b: any) => (a.displayName > b.displayName) ? 1 : -1);
this.wizard.addDropdownValues(
this._azureRegionsDropdown,
response.data.value.filter((value: any) => {
return supportedRegionsArray.includes(value.name);
}).map((value: any) => {
return {
displayName: value.displayName,
name: value.name
};
})
);
this.wizard.model.azureRegion = (this._azureRegionsDropdown.value as azdata.CategoryValue).name;
this._azureRegionsDropdown.loading = false;
}
}

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WizardPageBase } from '../../wizardPageBase';
import { DeployAzureSQLVMWizard } from '../deployAzureSQLVMWizard';
export abstract class BasePage extends WizardPageBase<DeployAzureSQLVMWizard> {
protected liveValidation!: boolean;
public initialize(): void {
throw new Error('Method not implemented.');
}
protected async validatePage(): Promise<string> {
return '';
}
protected activateRealTimeFormValidation(): void {
if (this.liveValidation) {
this.validatePage();
}
}
}

View File

@@ -0,0 +1,478 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { DeployAzureSQLVMWizard } from '../deployAzureSQLVMWizard';
import * as constants from '../constants';
import { BasePage } from './basePage';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class NetworkSettingsPage extends BasePage {
// virtual network components
private _newVirtualNetworkCheckbox!: azdata.CheckBoxComponent;
private _virtualNetworkFlexContainer !: azdata.FlexContainer;
private _virtualNetworkDropdown!: azdata.DropDownComponent;
private _newVirtualNetworkText!: azdata.InputBoxComponent;
// subnet network components
private _newSubnetCheckbox!: azdata.CheckBoxComponent;
private _subnetFlexContainer !: azdata.FlexContainer;
private _subnetDropdown!: azdata.DropDownComponent;
private _newsubnetText!: azdata.InputBoxComponent;
// public ip components
private _newPublicIpCheckbox!: azdata.CheckBoxComponent;
private _publicIpFlexContainer !: azdata.FlexContainer;
private _publicIpDropdown!: azdata.DropDownComponent;
private _publicIpNetworkText!: azdata.InputBoxComponent;
// checkbox for RDP
private _vmRDPAllowCheckbox!: azdata.CheckBoxComponent;
private _form!: azdata.FormContainer;
constructor(wizard: DeployAzureSQLVMWizard) {
super(
constants.NetworkSettingsPageTitle,
constants.NetworkSettingsPageDescription,
wizard
);
}
public async initialize() {
this.pageObject.registerContent(async (view: azdata.ModelView) => {
await Promise.all([
this.createVirtualNetworkDropdown(view),
this.createSubnetDropdown(view),
this.createPublicIPDropdown(view),
this.createVmRDPAllowCheckbox(view)
]);
this._form = view.modelBuilder.formContainer()
.withFormItems(
[
{
component: this.wizard.createFormRowComponent(view, constants.VirtualNetworkDropdownLabel, '', this._virtualNetworkFlexContainer, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.SubnetDropdownLabel, '', this._subnetFlexContainer, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.PublicIPDropdownLabel, '', this._publicIpFlexContainer, true)
},
{
component: this._vmRDPAllowCheckbox
}
],
{
horizontal: false,
componentWidth: '100%'
})
.withLayout({ width: '100%' })
.component();
return view.initializeModel(this._form);
});
}
public async onEnter(): Promise<void> {
this.populateVirtualNetworkDropdown();
this.populatePublicIpkDropdown();
this.liveValidation = false;
this.wizard.wizardObject.registerNavigationValidator(async (pcInfo) => {
if (pcInfo.newPage < pcInfo.lastPage) {
return true;
}
this.liveValidation = true;
let errorMessage = await this.validatePage();
if (errorMessage !== '') {
return false;
}
return true;
});
}
public async onLeave(): Promise<void> {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
}
private async createVirtualNetworkDropdown(view: azdata.ModelView) {
this._newVirtualNetworkCheckbox = view.modelBuilder.checkBox().withProperties(<azdata.CheckBoxProperties>{
label: constants.NetworkSettingsNewVirtualNetwork,
checked: false
}).component();
this._newVirtualNetworkCheckbox.onChanged((event) => {
this.toggleNewVirtualNetwork();
});
this._virtualNetworkDropdown = view.modelBuilder.dropDown().withProperties({
width: constants.standardWidth,
required: true
}).component();
this._virtualNetworkDropdown.onValueChanged((value) => {
this.wizard.model.virtualNetworkName = (this._virtualNetworkDropdown.value as azdata.CategoryValue).name;
this.populateSubnetDropdown();
});
this._newVirtualNetworkText = view.modelBuilder.inputBox().withProperties(<azdata.InputBoxProperties>{
width: constants.standardWidth,
required: true,
placeHolder: localize('deployAzureSQLVM.NewVnetPlaceholder', "Enter name for new virtual network")
}).component();
this._newVirtualNetworkText.onTextChanged((e) => {
this.wizard.model.virtualNetworkName = e;
this.activateRealTimeFormValidation();
});
this._virtualNetworkFlexContainer = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
}).withItems(
[this._virtualNetworkDropdown, this._newVirtualNetworkText, this._newVirtualNetworkCheckbox]
).component();
}
private async populateVirtualNetworkDropdown() {
this._virtualNetworkDropdown.loading = true;
let vnets = await this.getVirtualNetworks();
if (!vnets || vnets.length === 0) {
vnets = [
{
displayName: 'None',
name: 'None'
}
];
this._virtualNetworkDropdown.updateProperties({
values: vnets
});
this._newVirtualNetworkCheckbox.checked = true;
this._newVirtualNetworkCheckbox.enabled = false;
this.toggleNewVirtualNetwork();
} else {
this._virtualNetworkDropdown.updateProperties({
values: vnets
});
this._newVirtualNetworkCheckbox.enabled = true;
this.toggleNewVirtualNetwork();
}
this._virtualNetworkDropdown.loading = false;
await this.populateSubnetDropdown();
}
private toggleNewVirtualNetwork() {
let newVirtualNetwork = this._newVirtualNetworkCheckbox.checked;
this.wizard.model.newVirtualNetwork = newVirtualNetwork ? 'True' : 'False';
if (newVirtualNetwork) {
this.wizard.changeComponentDisplay(this._virtualNetworkDropdown, 'none');
this.wizard.changeComponentDisplay(this._newVirtualNetworkText, 'block');
this._newSubnetCheckbox.enabled = false;
this.wizard.changeComponentDisplay(this._subnetDropdown, 'none');
this.wizard.changeComponentDisplay(this._newsubnetText, 'block');
this.wizard.model.virtualNetworkName = this._newVirtualNetworkText.value!;
this.wizard.model.newSubnet = 'True';
this.wizard.model.subnetName = this._newsubnetText.value!;
} else {
this.wizard.changeComponentDisplay(this._virtualNetworkDropdown, 'block');
this.wizard.changeComponentDisplay(this._newVirtualNetworkText, 'none');
this._newSubnetCheckbox.enabled = true;
this.wizard.changeComponentDisplay(this._subnetDropdown, 'block');
this.wizard.changeComponentDisplay(this._newsubnetText, 'none');
this.wizard.model.virtualNetworkName = (this._virtualNetworkDropdown.value as azdata.CategoryValue).name;
this.wizard.model.newSubnet = this._newSubnetCheckbox.checked! ? 'True' : 'False';
}
}
private async createSubnetDropdown(view: azdata.ModelView) {
this._newSubnetCheckbox = view.modelBuilder.checkBox().withProperties<azdata.CheckBoxProperties>({
label: constants.NetworkSettingsNewSubnet,
checked: false
}).component();
this._newSubnetCheckbox.onChanged((value) => {
this.toggleNewSubnet();
});
this._subnetDropdown = view.modelBuilder.dropDown().withProperties(<azdata.DropDownProperties>{
width: constants.standardWidth,
required: true
}).component();
this._subnetDropdown.onValueChanged((value) => {
this.wizard.model.subnetName = (this._subnetDropdown.value as azdata.CategoryValue).name;
});
this._newsubnetText = view.modelBuilder.inputBox().withProperties(<azdata.InputBoxProperties>{
width: constants.standardWidth,
required: true,
placeHolder: localize('deployAzureSQLVM.NewSubnetPlaceholder', "Enter name for new subnet")
}).component();
this._newsubnetText.onTextChanged((e) => {
this.wizard.model.subnetName = e;
this.activateRealTimeFormValidation();
});
this._subnetFlexContainer = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
}).withItems(
[this._subnetDropdown, this._newsubnetText, this._newSubnetCheckbox]
).component();
}
private async populateSubnetDropdown() {
this._subnetDropdown.loading = true;
let subnets = await this.getSubnets();
if (!subnets || subnets.length === 0) {
subnets = [{
displayName: 'None',
name: 'None'
}];
this._subnetDropdown.updateProperties({
values: subnets
});
this._newSubnetCheckbox.checked = true;
this._newSubnetCheckbox.enabled = false;
this.toggleNewSubnet();
} else {
this._subnetDropdown.updateProperties({
values: subnets
});
this._newSubnetCheckbox.enabled = true;
this.toggleNewSubnet();
}
this._subnetDropdown.loading = false;
}
private toggleNewSubnet() {
let newSubnet = this._newSubnetCheckbox.checked!;
this.wizard.model.newSubnet = newSubnet ? 'True' : 'False';
if (newSubnet) {
this.wizard.changeComponentDisplay(this._subnetDropdown, 'none');
this.wizard.changeComponentDisplay(this._newsubnetText, 'block');
this.wizard.model.subnetName = this._newsubnetText.value!;
} else {
this.wizard.changeComponentDisplay(this._subnetDropdown, 'block');
this.wizard.changeComponentDisplay(this._newsubnetText, 'none');
this.wizard.model.subnetName = (this._subnetDropdown.value as azdata.CategoryValue).name;
}
}
private async createPublicIPDropdown(view: azdata.ModelView) {
this._newPublicIpCheckbox = view.modelBuilder.checkBox().withProperties(<azdata.CheckBoxProperties>{
label: constants.NetworkSettingsNewPublicIp,
checked: false
}).component();
this._newPublicIpCheckbox.onChanged((event) => {
this.toggleNewPublicIp();
});
this._publicIpDropdown = view.modelBuilder.dropDown().withProperties({
required: true,
width: constants.standardWidth,
}).component();
this._publicIpDropdown.onValueChanged((value) => {
this.wizard.model.publicIpName = (this._publicIpDropdown.value as azdata.CategoryValue).name;
});
this._publicIpNetworkText = view.modelBuilder.inputBox().withProperties(<azdata.InputBoxProperties>{
placeHolder: localize('deployAzureSQLVM.NewPipPlaceholder', "Enter name for new public IP"),
width: constants.standardWidth
}).component();
this._publicIpNetworkText.onTextChanged((e) => {
this.wizard.model.publicIpName = e;
this.activateRealTimeFormValidation();
});
this.wizard.changeComponentDisplay(this._publicIpNetworkText, 'none');
this._publicIpFlexContainer = view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column',
}).withItems(
[this._publicIpDropdown, this._publicIpNetworkText, this._newPublicIpCheckbox]
).component();
}
private async populatePublicIpkDropdown() {
this._publicIpDropdown.loading = true;
let publicIps = await this.getPips();
if (!publicIps || publicIps.length === 0) {
publicIps = [{
displayName: 'None',
name: 'None'
}];
this._publicIpDropdown.updateProperties({
values: publicIps
});
this._newPublicIpCheckbox.checked = true;
this._newPublicIpCheckbox.enabled = false;
this.toggleNewPublicIp();
} else {
this._publicIpDropdown.updateProperties({
values: publicIps
});
this._newPublicIpCheckbox.enabled = true;
this.toggleNewPublicIp();
}
this._publicIpDropdown.loading = false;
}
private toggleNewPublicIp() {
let newPip = this._newPublicIpCheckbox.checked!;
this.wizard.model.newPublicIp = newPip ? 'True' : 'False';
if (newPip) {
this.wizard.changeComponentDisplay(this._publicIpDropdown, 'none');
this.wizard.changeComponentDisplay(this._publicIpNetworkText, 'block');
this.wizard.model.publicIpName = this._publicIpNetworkText.value!;
} else {
this.wizard.changeComponentDisplay(this._publicIpDropdown, 'block');
this.wizard.changeComponentDisplay(this._publicIpNetworkText, 'none');
this.wizard.model.publicIpName = (this._publicIpDropdown.value as azdata.CategoryValue).name;
}
}
private async createVmRDPAllowCheckbox(view: azdata.ModelView) {
this._vmRDPAllowCheckbox = view.modelBuilder.checkBox().withProperties({
label: constants.RDPAllowCheckboxLabel,
}).component();
this._vmRDPAllowCheckbox.onChanged((value) => {
this.wizard.model.allowRDP = (value) ? 'True' : 'False';
});
this.wizard.model.allowRDP = 'False';
}
public async getVirtualNetworks(): Promise<any> {
let url = `https://management.azure.com` +
`/subscriptions/${this.wizard.model.azureSubscription}` +
`/providers/Microsoft.Network/virtualNetworks?api-version=2020-05-01`;
let response = await this.wizard.getRequest(url);
let dropdownValues = response.data.value.filter((value: any) => {
return value.location === this.wizard.model.azureRegion;
}).map((value: any) => {
let resourceGroupName = value.id.replace(RegExp('^(.*?)/resourceGroups/'), '').replace(RegExp('/providers/.*'), '');
return {
name: value.id,
displayName: `${value.name} \t\t resource group: (${resourceGroupName})`
};
});
return dropdownValues;
}
public async getSubnets(): Promise<any> {
if (!this.wizard.model.virtualNetworkName) {
return;
}
let url = `https://management.azure.com` +
`${this.wizard.model.virtualNetworkName}` +
`/subnets?api-version=2020-05-01`;
let response = await this.wizard.getRequest(url);
let dropdownValues = response.data.value.map((value: any) => {
return {
name: value.id,
displayName: `${value.name}`
};
});
return dropdownValues;
}
public async getPips(): Promise<any> {
let url = `https://management.azure.com` +
`/subscriptions/${this.wizard.model.azureSubscription}` +
`/providers/Microsoft.Network/publicIPAddresses?api-version=2020-05-01`;
let response = await this.wizard.getRequest(url);
let dropdownValues = response.data.value.filter((value: any) => {
return value.location === this.wizard.model.azureRegion;
}).map((value: any) => {
let resourceGroupName = value.id.replace(RegExp('^(.*?)/resourceGroups/'), '').replace(RegExp('/providers/.*'), '');
return {
name: value.id,
displayName: `${value.name} \t\t resource group: (${resourceGroupName})`
};
});
return dropdownValues;
}
protected async validatePage(): Promise<string> {
const errorMessages = [];
if (this.wizard.model.newVirtualNetwork === 'True') {
if (this.wizard.model.virtualNetworkName.length < 2 || this.wizard.model.virtualNetworkName.length > 64) {
errorMessages.push(localize('deployAzureSQLVM.VnetNameLengthError', "Virtual Network name must be between 2 and 64 characters long"));
}
} else {
if (this.wizard.model.virtualNetworkName === 'None') {
errorMessages.push(localize('deployAzureSQLVM.NewVnetError', "Create a new virtual network"));
}
}
if (this.wizard.model.newSubnet === 'True') {
if (this.wizard.model.subnetName.length < 1 || this.wizard.model.subnetName.length > 80) {
errorMessages.push(localize('deployAzureSQLVM.SubnetNameLengthError', "Subnet name must be between 1 and 80 characters long"));
}
} else {
if (this.wizard.model.subnetName === 'None') {
errorMessages.push(localize('deployAzureSQLVM.NewSubnetError', "Create a new sub network"));
}
}
if (this.wizard.model.newPublicIp === 'True') {
if (this.wizard.model.publicIpName.length < 1 || this.wizard.model.publicIpName.length > 80) {
errorMessages.push(localize('deployAzureSQLVM.PipNameError', "Public IP name must be between 1 and 80 characters long"));
}
} else {
if (this.wizard.model.publicIpName === 'None') {
errorMessages.push(localize('deployAzureSQLVM.NewPipError', "Create a new new public Ip"));
}
}
this.wizard.showErrorMessage(errorMessages.join('\n'));
return errorMessages.join('\n');
}
}

View File

@@ -0,0 +1,249 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { EOL } from 'os';
import * as constants from '../constants';
import { DeployAzureSQLVMWizard } from '../deployAzureSQLVMWizard';
import { BasePage } from './basePage';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class SqlServerSettingsPage extends BasePage {
private _sqlConnectivityDropdown!: azdata.DropDownComponent;
private _portTextRow!: azdata.FlexContainer;
private _portTextBox!: azdata.InputBoxComponent;
private _sqlAuthenticationDropdown!: azdata.DropDownComponent;
private _sqlAuthenticationTextbox!: azdata.InputBoxComponent;
private _sqlAuthenticationTextRow!: azdata.FlexContainer;
private _sqlAuthenticationPasswordTextbox!: azdata.InputBoxComponent;
private _sqlAuthenticationPasswordTextRow!: azdata.FlexContainer;
private _sqlAuthenticationPasswordConfirmationTextbox!: azdata.InputBoxComponent;
private _sqlAuthenticationPasswordConfirmationTextRow!: azdata.FlexContainer;
//private _sqlStorageOptimiazationDropdown!: azdata.DropDownComponent;
//private sqlStorageContainer!: azdata.FlexContainer;
private _form!: azdata.FormContainer;
constructor(wizard: DeployAzureSQLVMWizard) {
super(
constants.SqlServerSettingsPageTitle,
'',
wizard
);
}
public async initialize() {
this.pageObject.registerContent(async (view: azdata.ModelView) => {
await Promise.all([
this.createSqlConnectivityDropdown(view),
this.createPortText(view),
this.createSqlAuthentication(view),
]);
this._form = view.modelBuilder.formContainer()
.withFormItems(
[
{
component: this.wizard.createFormRowComponent(view, constants.SqlConnectivityTypeDropdownLabel, '', this._sqlConnectivityDropdown, true)
},
{
component: this._portTextRow
},
{
component: this.wizard.createFormRowComponent(view, constants.SqlEnableSQLAuthenticationLabel, '', this._sqlAuthenticationDropdown, true)
},
{
component: this._sqlAuthenticationTextRow
},
{
component: this._sqlAuthenticationPasswordTextRow
},
{
component: this._sqlAuthenticationPasswordConfirmationTextRow
}
],
{
horizontal: false,
componentWidth: '100%'
})
.withLayout({ width: '100%' })
.component();
return view.initializeModel(this._form);
});
}
public async onEnter(): Promise<void> {
this.liveValidation = false;
this.wizard.wizardObject.registerNavigationValidator(async (pcInfo) => {
if (pcInfo.newPage < pcInfo.lastPage) {
return true;
}
this.liveValidation = true;
let showErrorMessage = await this.validatePage();
if (showErrorMessage !== '') {
return false;
}
return true;
});
}
public async onLeave(): Promise<void> {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
}
private createSqlConnectivityDropdown(view: azdata.ModelView) {
const privateOptionDisplayName = localize('deployAzureSQLVM.PrivateConnectivityDropdownOptionDefault', "Private (within Virtual Network)");
this._sqlConnectivityDropdown = view.modelBuilder.dropDown().withProperties(<azdata.DropDownProperties>
{
values: [
{
name: 'local',
displayName: localize('deployAzureSQLVM.LocalConnectivityDropdownOption', "Local (inside VM only)")
},
{
name: 'private',
displayName: privateOptionDisplayName
},
{
name: 'public',
displayName: localize('deployAzureSQLVM.PublicConnectivityDropdownOption', "Public (Internet)")
}
],
value: {
name: 'private',
displayName: privateOptionDisplayName
}
}).component();
this.wizard.model.sqlConnectivityType = (this._sqlConnectivityDropdown.value as azdata.CategoryValue).name;
this._sqlConnectivityDropdown.onValueChanged((value) => {
let connectivityValue = (this._sqlConnectivityDropdown.value as azdata.CategoryValue).name;
this.wizard.model.sqlConnectivityType = connectivityValue;
if (connectivityValue === 'local') {
this.wizard.changeRowDisplay(this._portTextRow, 'none');
} else {
this.wizard.changeRowDisplay(this._portTextRow, 'block');
}
});
}
private createPortText(view: azdata.ModelView) {
this._portTextBox = view.modelBuilder.inputBox().withProperties(<azdata.InputBoxProperties>{
inputType: 'number',
max: 65535,
min: 1024,
value: '1433'
}).component();
this._portTextBox.onTextChanged((value) => {
this.wizard.model.port = value;
this.activateRealTimeFormValidation();
});
this._portTextRow = this.wizard.createFormRowComponent(view, constants.SqlPortLabel, '', this._portTextBox, true);
}
private createSqlAuthentication(view: azdata.ModelView) {
this._sqlAuthenticationDropdown = view.modelBuilder.dropDown().withProperties(<azdata.DropDownComponent>{
values: [
{
displayName: localize('deployAzureSQLVM.EnableSqlAuthenticationYesOption', "Yes"),
name: 'True'
},
{
displayName: localize('deployAzureSQLVM.EnableSqlAuthenticationNoOption', "No"),
name: 'False'
}
]
}).component();
this._sqlAuthenticationDropdown.onValueChanged((value) => {
let dropdownValue = (this._sqlAuthenticationDropdown.value as azdata.CategoryValue).name;
let displayValue: 'block' | 'none' = dropdownValue === 'True' ? 'block' : 'none';
this.wizard.changeRowDisplay(this._sqlAuthenticationTextRow, displayValue);
this.wizard.changeRowDisplay(this._sqlAuthenticationPasswordTextRow, displayValue);
this.wizard.changeRowDisplay(this._sqlAuthenticationPasswordConfirmationTextRow, displayValue);
this.wizard.model.enableSqlAuthentication = dropdownValue;
});
this.wizard.model.enableSqlAuthentication = (this._sqlAuthenticationDropdown.value as azdata.CategoryValue).name;
this._sqlAuthenticationTextbox = view.modelBuilder.inputBox().component();
this._sqlAuthenticationTextRow = this.wizard.createFormRowComponent(view, constants.SqlAuthenticationUsernameLabel, '', this._sqlAuthenticationTextbox, true);
this._sqlAuthenticationPasswordTextbox = view.modelBuilder.inputBox().withProperties(<azdata.InputBoxProperties>{
inputType: 'password'
}).component();
this._sqlAuthenticationPasswordTextRow = this.wizard.createFormRowComponent(view, constants.SqlAuthenticationPasswordLabel, '', this._sqlAuthenticationPasswordTextbox, true);
this._sqlAuthenticationPasswordConfirmationTextbox = view.modelBuilder.inputBox().withProperties(<azdata.InputBoxProperties>{
inputType: 'password'
}).component();
this._sqlAuthenticationPasswordConfirmationTextRow = this.wizard.createFormRowComponent(view, constants.SqlAuthenticationConfirmPasswordLabel, '', this._sqlAuthenticationPasswordConfirmationTextbox, true);
this._sqlAuthenticationTextbox.onTextChanged((value) => {
this.wizard.model.sqlAuthenticationUsername = value;
this.activateRealTimeFormValidation();
});
this._sqlAuthenticationPasswordTextbox.onTextChanged((value) => {
this.wizard.model.sqlAuthenticationPassword = value;
this.activateRealTimeFormValidation();
});
}
protected async validatePage(): Promise<string> {
const errorMessages = [];
if ((this._sqlAuthenticationDropdown.value as azdata.CategoryValue).name === 'True') {
let username = this._sqlAuthenticationTextbox.value!;
if (username.length < 2 || username.length > 128) {
errorMessages.push(localize('deployAzureSQLVM.SqlUsernameLengthError', "Username must be between 2 and 128 characters long."));
}
if (/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/.test(username)) {
errorMessages.push(localize('deployAzureSQLVM.SqlUsernameSpecialCharError', "Username cannot contain special characters \/\"\"[]:|<>+=;,?* ."));
}
errorMessages.push(this.wizard.validatePassword(this._sqlAuthenticationPasswordTextbox.value!));
if (this._sqlAuthenticationPasswordTextbox.value !== this._sqlAuthenticationPasswordConfirmationTextbox.value) {
errorMessages.push(localize('deployAzureSQLVM.SqlConfirmPasswordError', "Password and confirm password must match."));
}
}
this.wizard.showErrorMessage(errorMessages.join(EOL));
return errorMessages.join(EOL);
}
}

View File

@@ -0,0 +1,367 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { WizardPageBase } from '../../wizardPageBase';
import { DeployAzureSQLVMWizard } from '../deployAzureSQLVMWizard';
import * as constants from '../constants';
import { SectionInfo, LabelPosition, FontWeight, FieldType } from '../../../interfaces';
import { createSection } from '../../modelViewUtils';
export class AzureSQLVMSummaryPage extends WizardPageBase<DeployAzureSQLVMWizard> {
private formItems: azdata.FormComponent[] = [];
private _form!: azdata.FormBuilder;
private _view!: azdata.ModelView;
constructor(wizard: DeployAzureSQLVMWizard) {
super(
'Summary',
'',
wizard
);
}
public async initialize() {
this.pageObject.registerContent(async (view: azdata.ModelView) => {
this._view = view;
this._form = view.modelBuilder.formContainer();
return view.initializeModel(this._form!.withLayout({ width: '100%' }).component());
});
}
public async onEnter(): Promise<void> {
this.formItems.forEach(item => {
this._form.removeFormItem(item);
});
this.formItems = [];
let model = this.wizard.model;
const labelWidth = '150px';
const inputWidth = '400px';
const fieldHeight = '20px';
const auzreSettingSection: SectionInfo = {
labelPosition: LabelPosition.Left,
labelWidth: labelWidth,
inputWidth: inputWidth,
fieldHeight: fieldHeight,
spaceBetweenFields: '0',
title: constants.AzureSettingsPageTitle,
rows: [
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.AzureAccountDropdownLabel,
defaultValue: model.azureAccount.displayInfo.displayName,
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.AzureAccountSubscriptionDropdownLabel,
defaultValue: model.azureSubscriptionDisplayName,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.AzureAccountResourceGroupDropdownLabel,
defaultValue: model.azureResouceGroup,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.AzureAccountRegionDropdownLabel,
defaultValue: model.azureRegion,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
}
]
};
const vmSettingSection: SectionInfo = {
labelPosition: LabelPosition.Left,
labelWidth: labelWidth,
inputWidth: inputWidth,
fieldHeight: fieldHeight,
title: constants.VmSettingsPageTitle,
rows: [
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.VmNameTextBoxLabel,
defaultValue: model.vmName,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.SqlAuthenticationUsernameLabel,
defaultValue: model.vmUsername,
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.VmImageDropdownLabel,
defaultValue: model.vmImage,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.VmSkuDropdownLabel,
defaultValue: model.vmImageSKU,
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.VmVersionDropdownLabel,
defaultValue: model.vmImageVersion,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.VmSizeDropdownLabel,
defaultValue: model.vmSize,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
},
]
};
const networkSettingSection: SectionInfo = {
labelPosition: LabelPosition.Left,
labelWidth: labelWidth,
inputWidth: inputWidth,
fieldHeight: fieldHeight,
title: constants.NetworkSettingsPageTitle,
rows: [
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.VirtualNetworkDropdownLabel,
defaultValue: ((model.newVirtualNetwork === 'True' ? '(new) ' : '') + this.processVnetName()),
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.SubnetDropdownLabel,
defaultValue: ((model.newSubnet === 'True' ? '(new) ' : '') + this.processSubnetName()),
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
]
},
{
items: [
{
type: FieldType.ReadonlyText,
label: constants.PublicIPDropdownLabel,
defaultValue: ((model.newPublicIp === 'True' ? '(new) ' : '') + this.processPublicIp()),
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
}
]
};
const sqlServerSettingsPage: SectionInfo = {
labelPosition: LabelPosition.Left,
labelWidth: labelWidth,
inputWidth: inputWidth,
fieldHeight: fieldHeight,
title: constants.SqlServerSettingsPageTitle,
rows: [
]
};
sqlServerSettingsPage.rows?.push({
items: [
{
type: FieldType.ReadonlyText,
label: constants.SqlConnectivityTypeDropdownLabel,
defaultValue: model.sqlConnectivityType,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
});
if (model.sqlConnectivityType !== 'local') {
sqlServerSettingsPage.rows?.push({
items: [
{
type: FieldType.ReadonlyText,
label: constants.SqlPortLabel,
defaultValue: constants.SqlPortLabel,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
});
}
sqlServerSettingsPage.rows?.push({
items: [
{
type: FieldType.ReadonlyText,
label: constants.SqlEnableSQLAuthenticationLabel,
defaultValue: (model.enableSqlAuthentication === 'True' ? 'Yes ' : 'No '),
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
});
if (model.enableSqlAuthentication === 'True') {
sqlServerSettingsPage.rows?.push({
items: [
{
type: FieldType.ReadonlyText,
label: constants.SqlAuthenticationUsernameLabel,
defaultValue: model.sqlAuthenticationUsername,
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
});
}
const createSectionFunc = async (sectionInfo: SectionInfo): Promise<azdata.FormComponent> => {
return {
title: '',
component: await createSection({
container: this.wizard.wizardObject,
inputComponents: {},
sectionInfo: sectionInfo,
view: this._view,
onNewDisposableCreated: () => { },
onNewInputComponentCreated: () => { },
onNewValidatorCreated: () => { },
toolsService: this.wizard.toolsService
})
};
};
const azureSection = await createSectionFunc(auzreSettingSection);
const vmSection = await createSectionFunc(vmSettingSection);
const networkSection = await createSectionFunc(networkSettingSection);
const sqlServerSection = await createSectionFunc(sqlServerSettingsPage);
this.formItems.push(azureSection, vmSection, networkSection, sqlServerSection);
this._form.addFormItems(this.formItems);
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
}
public async onLeave(): Promise<void> {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
}
public createSummaryRow(view: azdata.ModelView, title: string, textComponent: azdata.TextComponent): azdata.FlexContainer {
const labelText = view.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>(
{
value: title,
width: '250px',
})
.component();
labelText.updateCssStyles({
'font-weight': '400',
'font-size': '13px',
});
const flexContainer = view.modelBuilder.flexContainer()
.withLayout(
{
flexFlow: 'row',
alignItems: 'center',
})
.withItems(
[labelText, textComponent],
{
CSSStyles: { 'margin-right': '5px' }
})
.component();
return flexContainer;
}
public processVnetName(): string {
if (this.wizard.model.newVirtualNetwork === 'True') {
return this.wizard.model.virtualNetworkName;
}
let resourceGroupName = this.wizard.model.virtualNetworkName.replace(RegExp('^(.*?)/resourceGroups/'), '').replace(RegExp('/providers/.*'), '');
let vnetName = this.wizard.model.virtualNetworkName.replace(RegExp('^(.*?)/virtualNetworks/'), '');
return `(${resourceGroupName}) ${vnetName}`;
}
public processSubnetName(): string {
if (this.wizard.model.newSubnet === 'True') {
return this.wizard.model.subnetName;
}
let subnetName = this.wizard.model.subnetName.replace(RegExp('^(.*?)/subnets/'), '');
return `${subnetName}`;
}
public processPublicIp(): string {
if (this.wizard.model.newPublicIp === 'True') {
return this.wizard.model.publicIpName;
}
let resourceGroupName = this.wizard.model.publicIpName.replace(RegExp('^(.*?)/resourceGroups/'), '').replace(RegExp('/providers/.*'), '');
let pipName = this.wizard.model.publicIpName.replace(RegExp('^(.*?)/publicIPAddresses/'), '');
return `(${resourceGroupName}) ${pipName}`;
}
}

View File

@@ -0,0 +1,467 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { EOL } from 'os';
import * as constants from '../constants';
import { DeployAzureSQLVMWizard } from '../deployAzureSQLVMWizard';
import { BasePage } from './basePage';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class VmSettingsPage extends BasePage {
private _vmSize: string[] = [];
// textbox for vm name
private _vmNameTextBox!: azdata.InputBoxComponent;
// textbox for vm admin username
private _adminUsernameTextBox!: azdata.InputBoxComponent;
// textbox for vm admin password
private _adminPasswordTextBox!: azdata.InputBoxComponent;
// textbox for vm admin confirm password
private _adminComfirmPasswordTextBox!: azdata.InputBoxComponent;
// dropdown for sql vm image
private _vmImageDropdown!: azdata.DropDownComponent;
// dropdown for sql vm image sku <- sql vm image
private _vmImageSkuDropdown!: azdata.DropDownComponent;
// dropdown for sql vm image version <- sql vm image sku
private _vmImageVersionDropdown!: azdata.DropDownComponent;
// dropdown for sql vm size
private _vmSizeDropdown!: azdata.DropDownComponent;
private _vmSizeLearnMoreLink!: azdata.HyperlinkComponent;
private _form!: azdata.FormContainer;
constructor(wizard: DeployAzureSQLVMWizard) {
super(
constants.VmSettingsPageTitle,
'',
wizard
);
}
public async initialize() {
this.pageObject.registerContent(async (view: azdata.ModelView) => {
await Promise.all([
this.createVmNameTextBox(view),
this.createAdminUsernameTextBox(view),
this.createAdminPasswordTextBox(view),
this.createAdminPasswordConfirmTextBox(view),
this.createVmImageDropdown(view),
this.createVMImageSkuDropdown(view),
this.createVMImageVersionDropdown(view),
this.createVmSizeDropdown(view),
]);
this.liveValidation = false;
this._form = view.modelBuilder.formContainer()
.withFormItems(
[
{
component: this.wizard.createFormRowComponent(view, constants.VmNameTextBoxLabel, '', this._vmNameTextBox, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.VmAdminUsernameTextBoxLabel, '', this._adminUsernameTextBox, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.VmAdminPasswordTextBoxLabel, '', this._adminPasswordTextBox, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.VmAdminConfirmPasswordTextBoxLabel, '', this._adminComfirmPasswordTextBox, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.VmImageDropdownLabel, '', this._vmImageDropdown, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.VmSkuDropdownLabel, '', this._vmImageSkuDropdown, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.VmVersionDropdownLabel, '', this._vmImageVersionDropdown, true)
},
{
component: this.wizard.createFormRowComponent(view, constants.VmSizeDropdownLabel, '', this._vmSizeDropdown, true)
},
{
component: this._vmSizeLearnMoreLink
}
],
{
horizontal: false,
componentWidth: '100%'
})
.withLayout({ width: '100%' })
.component();
return view.initializeModel(this._form);
});
}
public async onEnter(): Promise<void> {
this.populateVmImageDropdown();
this.populateVmSizeDropdown();
this.liveValidation = false;
this.wizard.wizardObject.registerNavigationValidator(async (pcInfo) => {
this.liveValidation = true;
if (pcInfo.newPage < pcInfo.lastPage) {
return true;
}
let errorMessage = await this.validatePage();
if (errorMessage !== '') {
return false;
}
return true;
});
}
public async onLeave(): Promise<void> {
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
}
private async createVmNameTextBox(view: azdata.ModelView) {
this._vmNameTextBox = view.modelBuilder.inputBox().withProperties({
}).component();
this._vmNameTextBox.onTextChanged((value) => {
this.wizard.model.vmName = value;
this.activateRealTimeFormValidation();
});
}
private async createAdminUsernameTextBox(view: azdata.ModelView) {
this._adminUsernameTextBox = view.modelBuilder.inputBox().withProperties({
}).component();
this._adminUsernameTextBox.onTextChanged((value) => {
this.wizard.model.vmUsername = value;
this.activateRealTimeFormValidation();
});
}
private async createAdminPasswordTextBox(view: azdata.ModelView) {
this._adminPasswordTextBox = view.modelBuilder.inputBox().withProperties({
inputType: 'password',
}).component();
this._adminPasswordTextBox.onTextChanged((value) => {
this.wizard.model.vmPassword = value;
this.activateRealTimeFormValidation();
});
}
private async createAdminPasswordConfirmTextBox(view: azdata.ModelView) {
this._adminComfirmPasswordTextBox = view.modelBuilder.inputBox().withProperties({
inputType: 'password',
}).component();
this._adminComfirmPasswordTextBox.onTextChanged((value) => {
this.activateRealTimeFormValidation();
});
}
private async createVmImageDropdown(view: azdata.ModelView) {
this._vmImageDropdown = view.modelBuilder.dropDown().withProperties({
}).component();
this._vmImageDropdown.onValueChanged((value) => {
this.wizard.model.vmImage = (this._vmImageDropdown.value as azdata.CategoryValue).name;
this._vmImageSkuDropdown.loading = true;
this._vmImageVersionDropdown.loading = true;
this.populateVmImageSkuDropdown();
});
}
private async populateVmImageDropdown() {
this._vmImageDropdown.loading = true;
this._vmImageSkuDropdown.loading = true;
this._vmImageVersionDropdown.loading = true;
let url = `https://management.azure.com` +
`/subscriptions/${this.wizard.model.azureSubscription}` +
`/providers/Microsoft.Compute` +
`/locations/${this.wizard.model.azureRegion}` +
`/publishers/MicrosoftSQLServer` +
`/artifacttypes/vmimage/offers` +
`?api-version=2019-12-01`;
let response = await this.wizard.getRequest(url, true);
response.data = response.data.reverse();
this.wizard.addDropdownValues(
this._vmImageDropdown,
response.data.filter((value: any) => {
return !new RegExp('-byol').test(value.name.toLowerCase());
})
.map((value: any) => {
let sqlServerVersion = value.name.toLowerCase().match(new RegExp('sql(.*?)-'))[1];
let osVersion = value.name.toLowerCase().replace(new RegExp('sql(.*?)-'), '');
osVersion = osVersion.replace(new RegExp('ws'), 'Windows Server ');
osVersion = osVersion.replace(new RegExp('ubuntu'), 'Ubuntu Server ');
osVersion = osVersion.replace(new RegExp('sles'), 'SUSE Linux Enterprise Server (SLES) ');
osVersion = osVersion.replace(new RegExp('rhel'), 'Red Hat Enterprise Linux ');
return {
displayName: `SQL Server ${sqlServerVersion.toUpperCase()} on ${osVersion}`,
name: value.name
};
})
);
this.wizard.model.vmImage = (this._vmImageDropdown.value as azdata.CategoryValue).name;
this._vmImageDropdown.loading = false;
this.populateVmImageSkuDropdown();
}
private async createVMImageSkuDropdown(view: azdata.ModelView) {
this._vmImageSkuDropdown = view.modelBuilder.dropDown().withProperties({
}).component();
this._vmImageSkuDropdown.onValueChanged((value) => {
this.wizard.model.vmImageSKU = (this._vmImageSkuDropdown.value as azdata.CategoryValue).name;
this.populateVmImageVersionDropdown();
});
}
private async populateVmImageSkuDropdown() {
this._vmImageSkuDropdown.loading = true;
let url = `https://management.azure.com` +
`/subscriptions/${this.wizard.model.azureSubscription}` +
`/providers/Microsoft.Compute` +
`/locations/${this.wizard.model.azureRegion}` +
`/publishers/MicrosoftSQLServer` +
`/artifacttypes/vmimage/offers/${this.wizard.model.vmImage}` +
`/skus?api-version=2019-12-01`;
let response = await this.wizard.getRequest(url, true);
this.wizard.addDropdownValues(
this._vmImageSkuDropdown,
response.data.map((value: any) => {
return {
name: value.name,
displayName: value.name
};
})
);
this.wizard.model.vmImageSKU = (this._vmImageSkuDropdown.value as azdata.CategoryValue).name;
this._vmImageSkuDropdown.loading = false;
this.populateVmImageVersionDropdown();
}
private async createVMImageVersionDropdown(view: azdata.ModelView) {
this._vmImageVersionDropdown = view.modelBuilder.dropDown().withProperties({
}).component();
this._vmImageVersionDropdown.onValueChanged((value) => {
this.wizard.model.vmImageVersion = (this._vmImageVersionDropdown.value as azdata.CategoryValue).name;
});
}
private async populateVmImageVersionDropdown() {
this._vmImageVersionDropdown.loading = true;
let url = `https://management.azure.com` +
`/subscriptions/${this.wizard.model.azureSubscription}` +
`/providers/Microsoft.Compute` +
`/locations/${this.wizard.model.azureRegion}` +
`/publishers/MicrosoftSQLServer` +
`/artifacttypes/vmimage/offers/${this.wizard.model.vmImage}` +
`/skus/${this.wizard.model.vmImageSKU}` +
`/versions?api-version=2019-12-01`;
let response = await this.wizard.getRequest(url, true);
this.wizard.addDropdownValues(
this._vmImageVersionDropdown,
response.data.map((value: any) => {
return {
name: value.name,
displayName: value.name
};
})
);
this.wizard.model.vmImageVersion = (this._vmImageVersionDropdown.value as azdata.CategoryValue).name;
this._vmImageVersionDropdown.loading = false;
}
private async createVmSizeDropdown(view: azdata.ModelView) {
this._vmSizeDropdown = view.modelBuilder.dropDown().withProperties({
editable: true
}).component();
this._vmSizeDropdown.onValueChanged((value) => {
this.wizard.model.vmSize = (this._vmSizeDropdown.value as azdata.CategoryValue).name;
});
this._vmSizeLearnMoreLink = view.modelBuilder.hyperlink().withProperties(<azdata.HyperlinkComponent>{
label: constants.VmSizeLearnMoreLabel,
url: 'https://go.microsoft.com/fwlink/?linkid=2143101'
}).component();
}
private async populateVmSizeDropdown() {
this._vmSizeDropdown.loading = true;
let url = `https://management.azure.com` +
`/subscriptions/${this.wizard.model.azureSubscription}` +
`/providers/Microsoft.Compute` +
`/skus?api-version=2019-04-01` +
`&$filter=location eq '${this.wizard.model.azureRegion}'`;
let response = await this.wizard.getRequest(url, true);
let vmResouces: any[] = [];
response.data.value.map((res: any) => {
if (res.resourceType === 'virtualMachines') {
vmResouces.push(res);
}
});
let dropDownValues = vmResouces.filter((value: any) => {
const discSize = Number(value.capabilities.filter((c: any) => { return c.name === 'MaxResourceVolumeMB'; })[0].value) / 1024;
if (discSize >= 40) {
return value;
}
}).map((value: any) => {
if (value.capabilities) {
let cores;
if (value.capabilities.filter((c: any) => { return c.name === 'vCPUsAvailable'; }).length !== 0) {
cores = value.capabilities.filter((c: any) => { return c.name === 'vCPUsAvailable'; })[0].value;
} else {
cores = value.capabilities.filter((c: any) => { return c.name === 'vCPUs'; })[0].value;
}
const memory = value.capabilities.filter((c: any) => { return c.name === 'MemoryGB'; })[0].value;
const discSize = Number(value.capabilities.filter((c: any) => { return c.name === 'MaxResourceVolumeMB'; })[0].value) / 1024;
const discCount = value.capabilities.filter((c: any) => { return c.name === 'MaxDataDiskCount'; })[0].value;
const displayText = `${value.name} Cores: ${cores} Memory: ${memory}GB discCount: ${discCount} discSize: ${discSize}GB`;
this._vmSize.push(displayText);
return {
name: value.name,
displayName: displayText
};
}
return;
});
dropDownValues.sort((a, b) => (a!.displayName > b!.displayName) ? 1 : -1);
this._vmSize = [];
this._vmSizeDropdown.updateProperties({
values: dropDownValues,
value: dropDownValues[0],
width: '480px'
});
this.wizard.model.vmSize = (this._vmSizeDropdown.value as azdata.CategoryValue).name;
this._vmSizeDropdown.loading = false;
}
protected async validatePage(): Promise<string> {
const errorMessages = [];
/**
* VM name rules:
* 1. 1-15 characters
* 2. Cannot contain only numbers
* 3. Cannot start with underscore and end with period or hyphen
* 4. Virtual machine name cannot contain special characters \/""[]:|<>+=;,?*
*/
let vmname = this.wizard.model.vmName;
if (vmname.length < 1 && vmname.length > 15) {
errorMessages.push(localize('deployAzureSQLVM.VnameLengthError', "Virtual machine name must be between 1 and 15 characters long."));
}
if (/^\d+$/.test(vmname)) {
errorMessages.push(localize('deployAzureSQLVM.VNameOnlyNumericNameError', "Virtual machine name cannot contain only numbers."));
}
if (vmname.charAt(0) === '_' || vmname.slice(-1) === '.' || vmname.slice(-1) === '-') {
errorMessages.push(localize('deployAzureSQLVM.VNamePrefixSuffixError', "Virtual machine name Can\'t start with underscore. Can\'t end with period or hyphen"));
}
if (/[\\\/"\'\[\]:\|<>\+=;,\?\*@\&,]/g.test(vmname)) {
errorMessages.push(localize('deployAzureSQLVM.VNameSpecialCharError', "Virtual machine name cannot contain special characters \/\"\"[]:|<>+=;,?*@&, ."));
}
if (await this.vmNameExists(vmname)) {
errorMessages.push(localize('deployAzureSQLVM.VNameExistsError', "Virtual machine name must be unique in the current resource group."));
}
/**
* VM admin/root username rules:
* 1. 1-20 characters long
* 2. cannot contain special characters \/""[]:|<>+=;,?*
*/
const reservedVMUsernames: string[] = [
'administrator', 'admin', 'user', 'user1', 'test', 'user2',
'test1', 'user3', 'admin1', '1', '123', 'a', 'actuser', 'adm', 'admin2',
'aspnet', 'backup', 'console', 'david', 'guest', 'john', 'owner', 'root', 'server', 'sql', 'support',
'support_388945a0', 'sys', 'test2', 'test3', 'user4', 'user5'
];
let username = this.wizard.model.vmUsername;
if (username.length < 1 || username.length > 20) {
errorMessages.push(localize('deployAzureSQLVM.VMUsernameLengthError', "Username must be between 1 and 20 characters long."));
}
if (username.slice(-1) === '.') {
errorMessages.push(localize('deployAzureSQLVM.VMUsernameSuffixError', 'Username cannot end with period'));
}
if (/[\\\/"\'\[\]:\|<>\+=;,\?\*@\&]/g.test(username)) {
errorMessages.push(localize('deployAzureSQLVM.VMUsernameSpecialCharError', "Username cannot contain special characters \/\"\"[]:|<>+=;,?*@& ."));
}
if (reservedVMUsernames.includes(username)) {
errorMessages.push(localize('deployAzureSQLVM.VMUsernameReservedWordsError', "Username must not include reserved words."));
}
errorMessages.push(this.wizard.validatePassword(this.wizard.model.vmPassword));
if (this.wizard.model.vmPassword !== this._adminComfirmPasswordTextBox.value) {
errorMessages.push(localize('deployAzureSQLVM.VMConfirmPasswordError', "Password and confirm password must match."));
}
if (this._vmSize.includes((this._vmSizeDropdown.value as azdata.CategoryValue).name)) {
errorMessages.push(localize('deployAzureSQLVM.vmDropdownSizeError', "Select a valid virtual machine size."));
}
this.wizard.showErrorMessage(errorMessages.join(EOL));
return errorMessages.join(EOL);
}
protected async vmNameExists(vmName: string): Promise<boolean> {
const url = `https://management.azure.com` +
`/subscriptions/${this.wizard.model.azureSubscription}` +
`/resourceGroups/${this.wizard.model.azureResouceGroup}` +
`/providers/Microsoft.Compute` +
`/virtualMachines?api-version=2019-12-01`;
let response = await this.wizard.getRequest(url, true);
let nameArray = response.data.value.map((v: any) => { return v.name; });
return (nameArray.includes(vmName));
}
}