mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 17:23:56 -05:00
Port updates for removing EULA acceptance checkbox from Arc deployments (#12409)
* controller dropdown field to SQL MIAA and Postgres deployment. (#12217) * saving first draft * throw if no controllers * cleanup * bug fixes * bug fixes and caching controller access * pr comments and bug fixes. * fixes * fixes * comment fix * remove debug prints * comment fixes * remove debug logs * inputValueTransformer returns string|Promise * PR feedback * pr fixes * remove _ from protected fields * anonymous to full methods * small fixes (cherry picked from commit9cf80113fc) * fix option sources (#12387) (cherry picked from commitfca8b85a72) * Remove azdata eula acceptance from arc deployments (#12292) * saving to switch tasks * activate to exports in extApi * working version - cleanup pending * improve messages * apply pr feedback from a different review * remove unneeded strings * redo apiService * remove async from getVersionFromOutput * remove _ prefix from protected fields * error message fix * throw specif errors from azdata extension * arrow methods to regular methods * pr feedback * expand azdata extension api * pr feedback * remove unused var * pr feedback (cherry picked from commitba44a2f02e) Co-authored-by: Arvind Ranasaria <ranasaria@outlook.com>
This commit is contained in:
83
extensions/resource-deployment/src/helpers/cacheManager.ts
Normal file
83
extensions/resource-deployment/src/helpers/cacheManager.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Deferred } from './promise';
|
||||
|
||||
const enum Status {
|
||||
notStarted,
|
||||
inProgress,
|
||||
done
|
||||
}
|
||||
|
||||
interface State<T> {
|
||||
entry?: T,
|
||||
error?: Error,
|
||||
status: Status,
|
||||
id: number,
|
||||
pendingOperation: Deferred<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of Cache Manager which ensures that only one call to populate cache miss is pending at a given time.
|
||||
* All remaining calls for retrieval are awaited until the one in progress finishes and then all awaited calls are resolved with the value
|
||||
* from the cache.
|
||||
*/
|
||||
export class CacheManager<K, T> {
|
||||
private _cache = new Map<K, State<T>>();
|
||||
private _id = 0;
|
||||
|
||||
public async getCacheEntry(key: K, retrieveEntry: (key: K) => Promise<T>): Promise<T> {
|
||||
const cacheHit: State<T> | undefined = this._cache.get(key);
|
||||
// each branch either throws or returns the password.
|
||||
if (cacheHit === undefined) {
|
||||
// populate a new state entry and add it to the cache
|
||||
const state: State<T> = {
|
||||
status: Status.notStarted,
|
||||
id: this._id++,
|
||||
pendingOperation: new Deferred<void>()
|
||||
};
|
||||
this._cache.set(key, state);
|
||||
// now that we have the state entry initialized, retry to fetch the cacheEntry
|
||||
let returnValue: T = await this.getCacheEntry(key, retrieveEntry);
|
||||
await state.pendingOperation;
|
||||
return returnValue!;
|
||||
} else {
|
||||
switch (cacheHit.status) {
|
||||
case Status.notStarted: {
|
||||
cacheHit.status = Status.inProgress;
|
||||
// retrieve and populate the missed cache hit.
|
||||
try {
|
||||
cacheHit.entry = await retrieveEntry(key);
|
||||
} catch (error) {
|
||||
cacheHit.error = error;
|
||||
} finally {
|
||||
cacheHit.status = Status.done;
|
||||
// we do not reject here even in error case because we do not want our awaits on pendingOperation to throw
|
||||
// We track our own error state and when all done we throw if an error had happened. This results
|
||||
// in the rejection of the promised returned by this method.
|
||||
cacheHit.pendingOperation.resolve();
|
||||
}
|
||||
return await this.getCacheEntry(key, retrieveEntry);
|
||||
}
|
||||
|
||||
case Status.inProgress: {
|
||||
await cacheHit.pendingOperation;
|
||||
return await this.getCacheEntry(key, retrieveEntry);
|
||||
}
|
||||
|
||||
case Status.done: {
|
||||
if (cacheHit.error !== undefined) {
|
||||
await cacheHit.pendingOperation;
|
||||
throw cacheHit.error;
|
||||
}
|
||||
else {
|
||||
await cacheHit.pendingOperation;
|
||||
return cacheHit.entry!;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
extensions/resource-deployment/src/helpers/optionSources.ts
Normal file
84
extensions/resource-deployment/src/helpers/optionSources.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as arc from 'arc';
|
||||
import { CategoryValue } from 'azdata';
|
||||
import { IOptionsSource } from '../interfaces';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { apiService } from '../services/apiService';
|
||||
import { throwUnless } from '../utils';
|
||||
import { CacheManager } from './cacheManager';
|
||||
|
||||
export enum OptionsSourceType {
|
||||
ArcControllersOptionsSource = 'ArcControllersOptionsSource'
|
||||
}
|
||||
|
||||
export abstract class OptionsSource implements IOptionsSource {
|
||||
|
||||
get type(): OptionsSourceType { return this._type; }
|
||||
get variableNames(): { [index: string]: string; } { return this._variableNames; }
|
||||
|
||||
abstract async getOptions(): Promise<string[] | CategoryValue[]>;
|
||||
abstract async getVariableValue(variableName: string, input: string): Promise<string>;
|
||||
abstract getIsPassword(variableName: string): boolean;
|
||||
|
||||
constructor(private _variableNames: { [index: string]: string }, private _type: OptionsSourceType) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ArcControllersOptionsSource extends OptionsSource {
|
||||
private _cacheManager = new CacheManager<string, string>();
|
||||
|
||||
async getOptions(): Promise<string[] | CategoryValue[]> {
|
||||
const controllers = await apiService.arcApi.getRegisteredDataControllers();
|
||||
throwUnless(controllers !== undefined && controllers.length !== 0, loc.noControllersConnected);
|
||||
return controllers.map(ci => {
|
||||
return ci.label;
|
||||
});
|
||||
}
|
||||
|
||||
async getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
|
||||
const retrieveVariable = async (key: string) => {
|
||||
const [variableName, controllerLabel] = JSON.parse(key);
|
||||
const controllers = await apiService.arcApi.getRegisteredDataControllers();
|
||||
const controller = controllers!.find(ci => ci.label === controllerLabel);
|
||||
throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel));
|
||||
switch (variableName) {
|
||||
case 'endpoint':
|
||||
return controller.info.url;
|
||||
case 'username':
|
||||
return controller.info.username;
|
||||
case 'password':
|
||||
const fetchedPassword = await this.getPassword(controller);
|
||||
return fetchedPassword;
|
||||
default:
|
||||
throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName));
|
||||
}
|
||||
};
|
||||
const variableValue = await this._cacheManager.getCacheEntry(JSON.stringify([variableName, controllerLabel]), retrieveVariable);
|
||||
return variableValue;
|
||||
}
|
||||
|
||||
private async getPassword(controller: arc.DataController): Promise<string> {
|
||||
let password = await apiService.arcApi.getControllerPassword(controller.info);
|
||||
if (!password) {
|
||||
password = await apiService.arcApi.reacquireControllerPassword(controller.info, password);
|
||||
}
|
||||
throwUnless(password !== undefined, loc.noPasswordFound(controller.label));
|
||||
return password;
|
||||
}
|
||||
|
||||
getIsPassword(variableName: string): boolean {
|
||||
switch (variableName) {
|
||||
case 'endpoint':
|
||||
case 'username':
|
||||
return false;
|
||||
case 'password':
|
||||
return true;
|
||||
default:
|
||||
throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName));
|
||||
}
|
||||
}
|
||||
}
|
||||
25
extensions/resource-deployment/src/helpers/promise.ts
Normal file
25
extensions/resource-deployment/src/helpers/promise.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Deferred promise
|
||||
*/
|
||||
export class Deferred<T> {
|
||||
promise: Promise<T>;
|
||||
resolve!: (value?: T | PromiseLike<T>) => void;
|
||||
reject!: (reason?: any) => void;
|
||||
constructor() {
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult> {
|
||||
return this.promise.then(onfulfilled, onrejected);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { OptionsSourceType } from './helpers/optionSources';
|
||||
|
||||
export const NoteBookEnvironmentVariablePrefix = 'AZDATA_NB_VAR_';
|
||||
|
||||
@@ -171,9 +172,18 @@ export type ComponentCSSStyles = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export interface IOptionsSource {
|
||||
readonly type: OptionsSourceType,
|
||||
readonly variableNames: { [index: string]: string; },
|
||||
getOptions(): Promise<string[] | azdata.CategoryValue[]>,
|
||||
getVariableValue(variableName: string, input: string): Promise<string>;
|
||||
getIsPassword(variableName: string): boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface OptionsInfo {
|
||||
values: string[] | azdata.CategoryValue[],
|
||||
values?: string[] | azdata.CategoryValue[],
|
||||
source?: IOptionsSource,
|
||||
defaultValue: string,
|
||||
optionsType?: OptionsType
|
||||
}
|
||||
@@ -363,6 +373,7 @@ export interface ITool {
|
||||
finishInitialization(): Promise<void>;
|
||||
install(): Promise<void>;
|
||||
isSameOrNewerThan(version: string): boolean;
|
||||
validateEula(): boolean;
|
||||
}
|
||||
|
||||
export const enum BdcDeploymentType {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { FieldType, OptionsType } from './interfaces';
|
||||
import { OptionsSourceType } from './helpers/optionSources';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -13,12 +15,23 @@ export const resourceGroup = localize('azure.account.resourceGroup', "Resource G
|
||||
export const location = localize('azure.account.location', "Azure Location");
|
||||
export const browse = localize('filePicker.browse', "Browse");
|
||||
export const select = localize('filePicker.select', "Select");
|
||||
export const kubeConfigFilePath = localize('kubeConfigClusterPicker.kubeConfigFilePatht', "Kube config file path");
|
||||
export const kubeConfigFilePath = localize('kubeConfigClusterPicker.kubeConfigFilePath', "Kube config file path");
|
||||
export const clusterContextNotFound = localize('kubeConfigClusterPicker.clusterContextNotFound', "No cluster context information found");
|
||||
export const signIn = localize('azure.signin', "Sign in…");
|
||||
export const refresh = localize('azure.refresh', "Refresh");
|
||||
export const createNewResourceGroup = localize('azure.resourceGroup.createNewResourceGroup', "Create a new resource group");
|
||||
export const NewResourceGroupAriaLabel = localize('azure.resourceGroup.NewResourceGroupAriaLabel', "New resource group name");
|
||||
export const realm = localize('deployCluster.Realm', "Realm");
|
||||
|
||||
|
||||
export const unexpectedOptionsSourceType = (type: OptionsSourceType) => localize('optionsSourceType.Invalid', "Invalid options source type:{0}", type);
|
||||
export const unknownFieldTypeError = (type: FieldType) => localize('UnknownFieldTypeError', "Unknown field type: \"{0}\"", type);
|
||||
export const variableValueFetchForUnsupportedVariable = (variableName: string) => localize('getVariableValue.unknownVariableName', "Attempt to get variable value for unknown variable:{0}", variableName);
|
||||
export const isPasswordFetchForUnsupportedVariable = (variableName: string) => localize('getIsPassword.unknownVariableName', "Attempt to get isPassword for unknown variable:{0}", variableName);
|
||||
export const noControllersConnected = localize('noControllersConnected', "No Azure ARC controllers are currently connected. Please run the command: 'Connect to Existing Azure Arc Controller' and then try again");
|
||||
export const noOptionsSourceDefined = (optionsSourceType: string) => localize('noOptionsSourceDefined', "No OptionsSource defined for type: {0}", optionsSourceType);
|
||||
export const noControllerInfoFound = (name: string) => localize('noControllerInfoFound', "controllerInfo could not be found with name: {0}", name);
|
||||
export const noPasswordFound = (controllerName: string) => localize('noPasswordFound', "Password could not be retrieved for controller: {0} and user did not provide a password. Please retry later.", controllerName);
|
||||
export const optionsNotDefined = (fieldType: FieldType) => localize('optionsNotDefined', "FieldInfo.options was not defined for field type: {0}", fieldType);
|
||||
export const optionsNotObjectOrArray = localize('optionsNotObjectOrArray', "FieldInfo.options must be an object if it is not an array");
|
||||
export const optionsTypeNotFound = localize('optionsTypeNotFound', "When FieldInfo.options is an object it must have 'optionsType' property");
|
||||
export const optionsTypeRadioOrDropdown = localize('optionsTypeRadioOrDropdown', "When optionsType is not {0} then it must be {1}", OptionsType.Radio, OptionsType.Dropdown);
|
||||
export const azdataEulaNotAccepted = localize('azdataEulaNotAccepted', "Deployment cannot continue. Azure Data CLI license terms have not been accepted. Execute the command: [Azure Data CLI: Accept EULA] to accept EULA to enable the features that requires Azure Data CLI.");
|
||||
|
||||
@@ -3,28 +3,22 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as arc from 'arc';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as azurecore from 'azurecore';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface IApiService {
|
||||
getAzurecoreApi(): Promise<azurecore.IExtension>;
|
||||
readonly azurecoreApi: azurecore.IExtension;
|
||||
readonly azdataApi: azdataExt.IExtension;
|
||||
readonly arcApi: arc.IExtension;
|
||||
}
|
||||
|
||||
class ApiService implements IApiService {
|
||||
|
||||
private azurecoreApi: azurecore.IExtension | undefined;
|
||||
|
||||
constructor() { }
|
||||
|
||||
public async getAzurecoreApi(): Promise<azurecore.IExtension> {
|
||||
if (!this.azurecoreApi) {
|
||||
this.azurecoreApi = <azurecore.IExtension>(await vscode.extensions.getExtension(azurecore.extension.name)?.activate());
|
||||
if (!this.azurecoreApi) {
|
||||
throw new Error('Unable to retrieve azurecore API');
|
||||
}
|
||||
}
|
||||
return this.azurecoreApi;
|
||||
}
|
||||
public get azurecoreApi() { return vscode.extensions.getExtension(azurecore.extension.name)?.exports; }
|
||||
public get azdataApi() { return vscode.extensions.getExtension(azdataExt.extension.name)?.exports; }
|
||||
public get arcApi() { return vscode.extensions.getExtension(arc.extension.name)?.exports; }
|
||||
}
|
||||
|
||||
export const apiService: IApiService = new ApiService();
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as arc from 'arc';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class ArcService {
|
||||
private _arcApi: arc.IExtension;
|
||||
constructor() {
|
||||
this._arcApi = vscode.extensions.getExtension(arc.extension.name)?.exports;
|
||||
}
|
||||
|
||||
public async getRegisteredDataControllers(): Promise<arc.ControllerInfo[]> {
|
||||
return await this._arcApi.getRegisteredDataControllers();
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,11 @@ import { SemVer } from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { AzdataInstallLocationKey, DeploymentConfigurationKey } from '../../constants';
|
||||
import { Command, OsDistribution, ToolType } from '../../interfaces';
|
||||
import { Command, OsDistribution, ToolStatus, ToolType } from '../../interfaces';
|
||||
import { apiService } from '../apiService';
|
||||
import { IPlatformService } from '../platformService';
|
||||
import { dependencyType, ToolBase } from './toolBase';
|
||||
import { SemVerProxy } from './SemVerProxy';
|
||||
import * as loc from '../../localizedConstants';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
export const AzdataToolName = 'azdata';
|
||||
@@ -44,25 +45,57 @@ export class AzdataTool extends ToolBase {
|
||||
return 'https://docs.microsoft.com/sql/big-data-cluster/deploy-install-azdata';
|
||||
}
|
||||
|
||||
public validateEula(): boolean {
|
||||
if (apiService.azdataApi.isEulaAccepted()) {
|
||||
return true;
|
||||
} else {
|
||||
this.setStatusDescription(loc.azdataEulaNotAccepted);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* unused */
|
||||
protected get versionCommand(): Command {
|
||||
return {
|
||||
command: 'azdata -v'
|
||||
command: ''
|
||||
};
|
||||
}
|
||||
|
||||
/* unused */
|
||||
protected get discoveryCommand(): Command {
|
||||
return {
|
||||
command: this.discoveryCommandString('azdata')
|
||||
command: ''
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the version and status for the tool.
|
||||
*/
|
||||
protected async updateVersionAndStatus(): Promise<void> {
|
||||
this.setStatusDescription('');
|
||||
await this.addInstallationSearchPathsToSystemPath();
|
||||
|
||||
const commandOutput = await apiService.azdataApi.azdata.version();
|
||||
this.version = apiService.azdataApi.azdata.getSemVersion();
|
||||
if (this.version) {
|
||||
if (this.autoInstallSupported) {
|
||||
// set the installationPath
|
||||
this.setInstallationPathOrAdditionalInformation(apiService.azdataApi.azdata.getPath());
|
||||
}
|
||||
this.setStatus(ToolStatus.Installed);
|
||||
}
|
||||
else {
|
||||
this.setInstallationPathOrAdditionalInformation(localize('deployCluster.GetToolVersionErrorInformation', "Error retrieving version information. See output channel '{0}' for more details", this.outputChannelName));
|
||||
this.setStatusDescription(localize('deployCluster.GetToolVersionError', "Error retrieving version information.{0}Invalid output received, get version command output: '{1}' ", EOL, commandOutput.stderr.join(EOL)));
|
||||
this.setStatus(ToolStatus.NotInstalled);
|
||||
}
|
||||
}
|
||||
|
||||
protected getVersionFromOutput(output: string): SemVer | undefined {
|
||||
let version: SemVer | undefined = undefined;
|
||||
if (output && output.split(EOL).length > 0) {
|
||||
version = new SemVerProxy(output.split(EOL)[0].replace(/ /g, ''));
|
||||
}
|
||||
return version;
|
||||
return apiService.azdataApi.azdata.getSemVersion();
|
||||
|
||||
}
|
||||
|
||||
protected async getSearchPaths(): Promise<string[]> {
|
||||
switch (this.osDistribution) {
|
||||
case OsDistribution.win32:
|
||||
|
||||
@@ -58,6 +58,8 @@ export abstract class ToolBase implements ITool {
|
||||
|
||||
protected abstract readonly versionCommand: Command;
|
||||
|
||||
public validateEula(): boolean { return true; }
|
||||
|
||||
public get dependencyMessages(): string[] {
|
||||
return (this.dependenciesByOsType.get(this.osDistribution) || []).map((msgType: dependencyType) => messageByDependencyType.get(msgType)!);
|
||||
}
|
||||
@@ -126,10 +128,18 @@ export abstract class ToolBase implements ITool {
|
||||
return this._statusDescription;
|
||||
}
|
||||
|
||||
protected setStatusDescription(value: string | undefined): void {
|
||||
this._statusDescription = value;
|
||||
}
|
||||
|
||||
public get installationPathOrAdditionalInformation(): string | undefined {
|
||||
return this._installationPathOrAdditionalInformation;
|
||||
}
|
||||
|
||||
protected setInstallationPathOrAdditionalInformation(value: string | undefined) {
|
||||
this._installationPathOrAdditionalInformation = value;
|
||||
}
|
||||
|
||||
protected get installationCommands(): Command[] | undefined {
|
||||
return this.allInstallationCommands.get(this.osDistribution);
|
||||
}
|
||||
@@ -250,7 +260,7 @@ export abstract class ToolBase implements ITool {
|
||||
/**
|
||||
* updates the version and status for the tool.
|
||||
*/
|
||||
private async updateVersionAndStatus(): Promise<void> {
|
||||
protected async updateVersionAndStatus(): Promise<void> {
|
||||
this._statusDescription = '';
|
||||
await this.addInstallationSearchPathsToSystemPath();
|
||||
const commandOutput = await this.platformService.runCommand(
|
||||
@@ -306,7 +316,7 @@ export abstract class ToolBase implements ITool {
|
||||
}
|
||||
|
||||
isSameOrNewerThan(version?: string): boolean {
|
||||
return !version || (this._version ? SemVerCompare(this._version, version) >= 0 : false);
|
||||
return !version || (this._version ? SemVerCompare(this._version.raw, version) >= 0 : false);
|
||||
}
|
||||
|
||||
private _pendingVersionAndStatusUpdate!: Promise<void>;
|
||||
|
||||
@@ -8,9 +8,8 @@ import assert = require('assert');
|
||||
import { apiService } from '../services/apiService';
|
||||
|
||||
suite('API Service Tests', function (): void {
|
||||
|
||||
test('getAzurecoreApi returns azure api', async () => {
|
||||
const api = await apiService.getAzurecoreApi();
|
||||
test('getAzurecoreApi returns azure api', () => {
|
||||
const api = apiService.azurecoreApi;
|
||||
assert(api !== undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
/// <reference path='../../../../src/sql/azdata.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
||||
/// <reference path='../../../arc/src/typings/arc.d.ts'/>
|
||||
/// <reference path='../../../azdata/src/typings/azdata-ext.d.ts'/>
|
||||
/// <reference path='../../../azurecore/src/azurecore.d.ts'/>
|
||||
/// <reference path='../../../azurecore/src/azureResource/azure-resource.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
|
||||
@@ -176,11 +176,11 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave(): void {
|
||||
public async onLeave(): Promise<void> {
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
});
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
await setModelValues(this.inputComponents, this.wizard.model);
|
||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,8 +289,8 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave() {
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
public async onLeave(): Promise<void> {
|
||||
await setModelValues(this.inputComponents, this.wizard.model);
|
||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||
const variableDNSPrefixMapping: { [s: string]: string } = {};
|
||||
|
||||
@@ -233,7 +233,7 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave() {
|
||||
public async onLeave(): Promise<void> {
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -400,8 +400,8 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave(): void {
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
public async onLeave(): Promise<void> {
|
||||
await setModelValues(this.inputComponents, this.wizard.model);
|
||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
|
||||
@@ -326,7 +326,7 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
this.form.addFormItems(this.formItems);
|
||||
}
|
||||
|
||||
public onLeave() {
|
||||
public async onLeave(): Promise<void> {
|
||||
this.wizard.hideCustomButtons();
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export class TargetClusterContextPage extends WizardPageBase<DeployClusterWizard
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave() {
|
||||
public async onLeave(): Promise<void> {
|
||||
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -94,9 +94,9 @@ export class DeploymentInputDialog extends DialogBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected onComplete(): void {
|
||||
protected async onComplete(): Promise<void> {
|
||||
const model: Model = new Model();
|
||||
setModelValues(this.inputComponents, model);
|
||||
await setModelValues(this.inputComponents, model);
|
||||
if (instanceOfNotebookBasedDialogInfo(this.dialogInfo)) {
|
||||
model.setEnvironmentVariables();
|
||||
if (this.dialogInfo.runNotebook) {
|
||||
@@ -110,7 +110,7 @@ export class DeploymentInputDialog extends DialogBase {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
vscode.commands.executeCommand(this.dialogInfo.command, model);
|
||||
await vscode.commands.executeCommand(this.dialogInfo.command, model);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,12 @@ export abstract class DialogBase {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
private onOkButtonClicked(): void {
|
||||
this.onComplete();
|
||||
private async onOkButtonClicked(): Promise<void> {
|
||||
await this.onComplete();
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
protected onComplete(): void {
|
||||
protected async onComplete(): Promise<void> {
|
||||
}
|
||||
|
||||
protected dispose(): void {
|
||||
|
||||
@@ -3,27 +3,28 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdata from 'azdata';
|
||||
import { azureResource } from 'azureResource';
|
||||
import * as fs from 'fs';
|
||||
import { EOL, homedir as os_homedir } from 'os';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
||||
import { ArcControllersOptionsSource, OptionsSourceType } from '../helpers/optionSources';
|
||||
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, IOptionsSource, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { apiService } from '../services/apiService';
|
||||
import { getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../services/kubeService';
|
||||
import { assert, getDateTimeString, getErrorMessage } from '../utils';
|
||||
import { KubeCtlTool, KubeCtlToolName } from '../services/tools/kubeCtlTool';
|
||||
import { IToolsService } from '../services/toolsService';
|
||||
import { getDateTimeString, getErrorMessage, throwUnless } from '../utils';
|
||||
import { WizardInfoBase } from './../interfaces';
|
||||
import { Model } from './model';
|
||||
import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder';
|
||||
import { IToolsService } from '../services/toolsService';
|
||||
import { KubeCtlToolName, KubeCtlTool } from '../services/tools/kubeCtlTool';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export type Validator = () => { valid: boolean, message: string };
|
||||
export type InputValueTransformer = (inputValue: string) => string;
|
||||
export type InputValueTransformer = (inputValue: string) => string | Promise<string>;
|
||||
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
||||
export type InputComponentInfo = {
|
||||
component: InputComponent;
|
||||
@@ -353,7 +354,7 @@ function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata
|
||||
async function processField(context: FieldContext): Promise<void> {
|
||||
switch (context.fieldInfo.type) {
|
||||
case FieldType.Options:
|
||||
processOptionsTypeField(context);
|
||||
await processOptionsTypeField(context);
|
||||
break;
|
||||
case FieldType.DateTimeText:
|
||||
processDateTimeTextField(context);
|
||||
@@ -390,12 +391,23 @@ async function processField(context: FieldContext): Promise<void> {
|
||||
await processKubeStorageClassField(context);
|
||||
break;
|
||||
default:
|
||||
throw new Error(localize('UnknownFieldTypeError', "Unknown field type: \"{0}\"", context.fieldInfo.type));
|
||||
throw new Error(loc.unknownFieldTypeError(context.fieldInfo.type));
|
||||
}
|
||||
}
|
||||
|
||||
function processOptionsTypeField(context: FieldContext): void {
|
||||
assert(context.fieldInfo.options !== undefined, `FieldInfo.options must be defined for FieldType:${FieldType.Options}`);
|
||||
function disableControlButtons(container: azdata.window.Dialog | azdata.window.Wizard): void {
|
||||
if ('okButton' in container) {
|
||||
container.okButton.enabled = false;
|
||||
} else {
|
||||
container.doneButton.enabled = false;
|
||||
container.nextButton.enabled = false;
|
||||
container.backButton.enabled = false;
|
||||
container.customButtons.forEach(b => b.enabled = false);
|
||||
}
|
||||
}
|
||||
|
||||
async function processOptionsTypeField(context: FieldContext): Promise<void> {
|
||||
throwUnless(context.fieldInfo.options !== undefined, loc.optionsNotDefined(context.fieldInfo.type));
|
||||
if (Array.isArray(context.fieldInfo.options)) {
|
||||
context.fieldInfo.options = <OptionsInfo>{
|
||||
values: context.fieldInfo.options,
|
||||
@@ -403,17 +415,69 @@ function processOptionsTypeField(context: FieldContext): void {
|
||||
optionsType: OptionsType.Dropdown
|
||||
};
|
||||
}
|
||||
assert(typeof context.fieldInfo.options === 'object', `FieldInfo.options must be an object if it is not an array`);
|
||||
assert('optionsType' in context.fieldInfo.options, `When FieldInfo.options is an object it must have 'optionsType' property`);
|
||||
throwUnless(typeof context.fieldInfo.options === 'object', loc.optionsNotObjectOrArray);
|
||||
throwUnless('optionsType' in context.fieldInfo.options, loc.optionsTypeNotFound);
|
||||
if (context.fieldInfo.options.source) {
|
||||
try {
|
||||
let optionsSource: IOptionsSource;
|
||||
switch (context.fieldInfo.options.source.type) {
|
||||
case OptionsSourceType.ArcControllersOptionsSource:
|
||||
optionsSource = new ArcControllersOptionsSource(context.fieldInfo.options.source.variableNames, context.fieldInfo.options.source.type);
|
||||
break;
|
||||
default:
|
||||
throw new Error(loc.noOptionsSourceDefined(context.fieldInfo.options.source.type));
|
||||
}
|
||||
context.fieldInfo.options.source = optionsSource;
|
||||
context.fieldInfo.options.values = await context.fieldInfo.options.source.getOptions();
|
||||
}
|
||||
catch (e) {
|
||||
disableControlButtons(context.container);
|
||||
context.container.message = {
|
||||
text: getErrorMessage(e),
|
||||
description: '',
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
context.fieldInfo.options.values = [];
|
||||
}
|
||||
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
|
||||
}
|
||||
let optionsComponent: InputComponent;
|
||||
if (context.fieldInfo.options.optionsType === OptionsType.Radio) {
|
||||
processRadioOptionsTypeField(context);
|
||||
optionsComponent = await processRadioOptionsTypeField(context);
|
||||
} else {
|
||||
assert(context.fieldInfo.options.optionsType === OptionsType.Dropdown, `When optionsType is not ${OptionsType.Radio} then it must be ${OptionsType.Dropdown}`);
|
||||
processDropdownOptionsTypeField(context);
|
||||
throwUnless(context.fieldInfo.options.optionsType === OptionsType.Dropdown, loc.optionsTypeRadioOrDropdown);
|
||||
optionsComponent = processDropdownOptionsTypeField(context);
|
||||
}
|
||||
if (context.fieldInfo.options.source) {
|
||||
const optionsSource = context.fieldInfo.options.source;
|
||||
for (const key of Object.keys(optionsSource.variableNames ?? {})) {
|
||||
context.fieldInfo.subFields!.push({
|
||||
label: context.fieldInfo.label,
|
||||
variableName: optionsSource.variableNames[key]
|
||||
});
|
||||
context.onNewInputComponentCreated(optionsSource.variableNames[key], {
|
||||
component: optionsComponent,
|
||||
inputValueTransformer: async (controllerName: string) => {
|
||||
try {
|
||||
const variableValue = await optionsSource.getVariableValue(key, controllerName);
|
||||
return variableValue;
|
||||
} catch (e) {
|
||||
disableControlButtons(context.container);
|
||||
context.container.message = {
|
||||
text: getErrorMessage(e),
|
||||
description: '',
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
return '';
|
||||
}
|
||||
},
|
||||
isPassword: optionsSource.getIsPassword(key)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processDropdownOptionsTypeField(context: FieldContext): void {
|
||||
function processDropdownOptionsTypeField(context: FieldContext): azdata.DropDownComponent {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const options = context.fieldInfo.options as OptionsInfo;
|
||||
const dropdown = createDropdown(context.view, {
|
||||
@@ -427,6 +491,7 @@ function processDropdownOptionsTypeField(context: FieldContext): void {
|
||||
dropdown.fireOnTextChange = true;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: dropdown });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo);
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
function processDateTimeTextField(context: FieldContext): void {
|
||||
@@ -579,8 +644,8 @@ function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
|
||||
const readOnlyField = processReadonlyTextField(context, false /*allowEvaluation*/);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
|
||||
component: readOnlyField.text!,
|
||||
inputValueTransformer: () => {
|
||||
readOnlyField.text!.value = substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
|
||||
inputValueTransformer: async () => {
|
||||
readOnlyField.text!.value = await substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
|
||||
return readOnlyField.text?.value!;
|
||||
}
|
||||
});
|
||||
@@ -596,14 +661,15 @@ function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
|
||||
* @param inputValue
|
||||
* @param inputComponents
|
||||
*/
|
||||
function substituteVariableValues(inputComponents: InputComponents, inputValue?: string): string | undefined {
|
||||
Object.keys(inputComponents)
|
||||
async function substituteVariableValues(inputComponents: InputComponents, inputValue?: string): Promise<string | undefined> {
|
||||
await Promise.all(Object.keys(inputComponents)
|
||||
.filter(key => key.startsWith(NoteBookEnvironmentVariablePrefix))
|
||||
.forEach(key => {
|
||||
const value = getInputComponentValue(inputComponents, key) ?? '<undefined>';
|
||||
.map(async key => {
|
||||
const value = (await getInputComponentValue(inputComponents, key)) ?? '<undefined>';
|
||||
const re: RegExp = new RegExp(`\\\$\\\(${key}\\\)`, 'gi');
|
||||
inputValue = inputValue?.replace(re, value);
|
||||
});
|
||||
})
|
||||
);
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
@@ -984,7 +1050,7 @@ async function handleSelectedAccountChanged(
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await (await apiService.getAzurecoreApi()).getSubscriptions(selectedAccount, true);
|
||||
const response = await apiService.azurecoreApi.getSubscriptions(selectedAccount, true);
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
@@ -1099,7 +1165,7 @@ async function handleSelectedSubscriptionChanged(context: AzureAccountFieldConte
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await (await apiService.getAzurecoreApi()).getResourceGroups(selectedAccount, selectedSubscription, true);
|
||||
const response = await apiService.azurecoreApi.getResourceGroups(selectedAccount, selectedSubscription, true);
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
@@ -1150,8 +1216,7 @@ async function processAzureLocationsField(context: AzureLocationsFieldContext):
|
||||
width: context.fieldInfo.labelWidth,
|
||||
cssStyles: context.fieldInfo.labelCSSStyles
|
||||
});
|
||||
const azurecoreApi = await apiService.getAzurecoreApi();
|
||||
const locationValues = context.fieldInfo.locations?.map(l => { return { name: l, displayName: azurecoreApi.getRegionDisplayName(l) }; });
|
||||
const locationValues = context.fieldInfo.locations?.map(l => { return { name: l, displayName: apiService.azurecoreApi.getRegionDisplayName(l) }; });
|
||||
const locationDropdown = createDropdown(context.view, {
|
||||
defaultValue: locationValues?.find(l => l.name === context.fieldInfo.defaultValue),
|
||||
width: context.fieldInfo.inputWidth,
|
||||
@@ -1174,7 +1239,7 @@ async function processAzureLocationsField(context: AzureLocationsFieldContext):
|
||||
label: label.value!,
|
||||
variableName: context.fieldInfo.displayLocationVariableName
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, { component: locationDropdown, inputValueTransformer: (value => azurecoreApi.getRegionDisplayName(value)) });
|
||||
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, { component: locationDropdown, inputValueTransformer: (value => apiService.azurecoreApi.getRegionDisplayName(value)) });
|
||||
}
|
||||
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo);
|
||||
return locationDropdown;
|
||||
@@ -1207,14 +1272,14 @@ export function getPasswordMismatchMessage(fieldName: string): string {
|
||||
return localize('passwordNotMatch', "{0} doesn't match the confirmation password", fieldName);
|
||||
}
|
||||
|
||||
export function setModelValues(inputComponents: InputComponents, model: Model): void {
|
||||
Object.keys(inputComponents).forEach(key => {
|
||||
const value = getInputComponentValue(inputComponents, key);
|
||||
export async function setModelValues(inputComponents: InputComponents, model: Model): Promise<void> {
|
||||
await Promise.all(Object.keys(inputComponents).map(async key => {
|
||||
const value = await getInputComponentValue(inputComponents, key);
|
||||
model.setPropertyValue(key, value);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
function getInputComponentValue(inputComponents: InputComponents, key: string): string | undefined {
|
||||
async function getInputComponentValue(inputComponents: InputComponents, key: string): Promise<string | undefined> {
|
||||
const input = inputComponents[key].component;
|
||||
if (input === undefined) {
|
||||
return undefined;
|
||||
@@ -1236,7 +1301,10 @@ function getInputComponentValue(inputComponents: InputComponents, key: string):
|
||||
}
|
||||
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
||||
if (inputValueTransformer) {
|
||||
value = inputValueTransformer(value || '');
|
||||
value = inputValueTransformer(value ?? '');
|
||||
if (typeof value !== 'string') {
|
||||
value = await value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPag
|
||||
}
|
||||
|
||||
protected async onOk(): Promise<void> {
|
||||
setModelValues(this.inputComponents, this.model);
|
||||
await setModelValues(this.inputComponents, this.model);
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
this.model.setEnvironmentVariables(env, (varName) => {
|
||||
const isPassword = !!this.inputComponents[varName]?.isPassword;
|
||||
|
||||
@@ -33,7 +33,7 @@ export class NotebookWizardAutoSummaryPage extends NotebookWizardPage {
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave(): void {
|
||||
public async onLeave(): Promise<void> {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave(): void {
|
||||
public async onLeave(): Promise<void> {
|
||||
// The following callback registration clears previous navigation validators.
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
@@ -66,7 +66,7 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
|
||||
|
||||
public async onEnter(): Promise<void> {
|
||||
if (this.pageInfo.isSummaryPage) {
|
||||
setModelValues(this.wizard.inputComponents, this.wizard.model);
|
||||
await setModelValues(this.wizard.inputComponents, this.wizard.model);
|
||||
}
|
||||
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
|
||||
@@ -26,6 +26,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
private _agreementContainer!: azdata.DivContainer;
|
||||
private _agreementCheckboxChecked: boolean = false;
|
||||
private _installToolButton: azdata.window.Button;
|
||||
private _recheckEulaButton: azdata.window.Button;
|
||||
private _installationInProgress: boolean = false;
|
||||
private _tools: ITool[] = [];
|
||||
|
||||
@@ -37,10 +38,15 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
super(localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
|
||||
this._selectedResourceType = defaultResourceType;
|
||||
this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools"));
|
||||
this._recheckEulaButton = azdata.window.createButton(localize('deploymentDialog.RecheckEulaButton', "Validate EULA"));
|
||||
this._toDispose.push(this._installToolButton.onClick(() => {
|
||||
this.installTools().catch(error => console.log(error));
|
||||
}));
|
||||
this._dialogObject.customButtons = [this._installToolButton];
|
||||
this._toDispose.push(this._recheckEulaButton.onClick(() => {
|
||||
this._dialogObject.message = { text: '' }; // clear any previous message.
|
||||
this._dialogObject.okButton.enabled = this.validateToolsEula(); // re-enable the okButton if validation succeeds.
|
||||
}));
|
||||
this._dialogObject.customButtons = [this._installToolButton, this._recheckEulaButton];
|
||||
this._installToolButton.hidden = true;
|
||||
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', "Select");
|
||||
this._dialogObject.okButton.enabled = false; // this is enabled after all tools are discovered.
|
||||
@@ -270,12 +276,12 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPathOrAdditionalInformation || ''];
|
||||
});
|
||||
this._installToolButton.hidden = erroredOrFailedTool || minVersionCheckFailed || (toolsToAutoInstall.length === 0);
|
||||
this._dialogObject.okButton.enabled = !erroredOrFailedTool && messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0);
|
||||
this._dialogObject.okButton.enabled = !erroredOrFailedTool && messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0) && this.validateToolsEula();
|
||||
if (messages.length !== 0) {
|
||||
if (messages.length > 1) {
|
||||
messages = messages.map(message => `• ${message}`);
|
||||
}
|
||||
messages.push(localize('deploymentDialog.VersionInformationDebugHint', "You will need to restart Azure Data Studio if the tools are installed manually after Azure Data Studio is launched to pick up the updated PATH environment variable. You may find additional details in 'Deployments' output channel"));
|
||||
messages.push(localize('deploymentDialog.VersionInformationDebugHint', "You will need to restart Azure Data Studio if the tools are installed manually to pick up the change. You may find additional details in 'Deployments' and 'azdata' output channels"));
|
||||
this._dialogObject.message = {
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
text: messages.join(EOL)
|
||||
@@ -305,6 +311,21 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
this._toolsLoadingComponent.loading = false;
|
||||
}
|
||||
|
||||
private validateToolsEula(): boolean {
|
||||
const validationSucceeded = this._tools.every(tool => {
|
||||
const eulaValidated = tool.validateEula();
|
||||
if (!eulaValidated) {
|
||||
this._dialogObject.message = {
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
text: tool.statusDescription!
|
||||
};
|
||||
}
|
||||
return eulaValidated;
|
||||
});
|
||||
this._recheckEulaButton.hidden = validationSucceeded;
|
||||
return validationSucceeded;
|
||||
}
|
||||
|
||||
private get toolRequirements() {
|
||||
return this.getCurrentProvider().requiredTools;
|
||||
}
|
||||
@@ -347,7 +368,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
return this._selectedResourceType.getProvider(options)!;
|
||||
}
|
||||
|
||||
protected onComplete(): void {
|
||||
protected async onComplete(): Promise<void> {
|
||||
this.toolsService.toolsForCurrentProvider = this._tools;
|
||||
this.resourceTypeService.startDeployment(this.getCurrentProvider());
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export abstract class WizardBase<T, P extends WizardPageBase<T>, M extends Model
|
||||
this.toDispose.push(this.wizardObject.onPageChanged(async (e) => {
|
||||
let previousPage = this.pages[e.lastPage];
|
||||
let newPage = this.pages[e.newPage];
|
||||
previousPage.onLeave();
|
||||
await previousPage.onLeave();
|
||||
await newPage.onEnter();
|
||||
}));
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export abstract class WizardPageBase<T> {
|
||||
|
||||
public async onEnter(): Promise<void> { }
|
||||
|
||||
public onLeave(): void { }
|
||||
public async onLeave(): Promise<void> { }
|
||||
|
||||
public abstract initialize(): void;
|
||||
|
||||
|
||||
@@ -40,7 +40,15 @@ export function setEnvironmentVariablesForInstallPaths(tools: ITool[], env: Node
|
||||
}
|
||||
}
|
||||
|
||||
export function assert(condition: boolean, message?: string): asserts condition {
|
||||
/**
|
||||
* Throws an Error with given {@link message} unless {@link condition} is true.
|
||||
* This also tells the typescript compiler that the condition is 'truthy' in the remainder of the scope
|
||||
* where this function was called.
|
||||
*
|
||||
* @param condition
|
||||
* @param message
|
||||
*/
|
||||
export function throwUnless(condition: boolean, message?: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user