mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-25 09:35:37 -05:00
allow registering options source providers to resource-deployment (#12712)
* first draft * compile fixes * uncomment code * waitForAzdataToolDisovery added to azdata api * missed change in last commit * remove switchReturn * contributeOptionsSource renamed * remove switchReturn reference * create optionSourceService * azdataTool usage more reliable * package.json fixes and cleanup * cleanup * revert 4831a6e6b8b08684488b2c9e18092fa252e3057f * pr feedback * pr feedback * pr feedback * cleanup * cleanup * fix eulaAccepted check * fix whitespade in doc comments.
This commit is contained in:
14
extensions/resource-deployment/src/api.ts
Normal file
14
extensions/resource-deployment/src/api.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as rd from 'resource-deployment';
|
||||
import { optionsSourcesService } from './services/optionSourcesService';
|
||||
|
||||
export function getExtensionApi(): rd.IExtension {
|
||||
return {
|
||||
registerOptionsSourceProvider: (provider: rd.IOptionsSourceProvider) => optionsSourcesService.registerOptionsSourceProvider(provider)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import { ToolsInstallPath } from './constants';
|
||||
import { ITool, NoteBookEnvironmentVariablePrefix } from './interfaces';
|
||||
import { ToolsInstallPath } from '../constants';
|
||||
import { ITool, NoteBookEnvironmentVariablePrefix } from '../interfaces';
|
||||
|
||||
export function getErrorMessage(error: any): string {
|
||||
return (error instanceof Error)
|
||||
@@ -1,83 +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 { 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!;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +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 { 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 const enum OptionsSourceType {
|
||||
ArcControllersOptionsSource = 'ArcControllersOptionsSource',
|
||||
ArcControllerConfigProfilesOptionsSource = 'ArcControllerConfigProfilesOptionsSource'
|
||||
}
|
||||
|
||||
export abstract class OptionsSource implements IOptionsSource {
|
||||
|
||||
get type(): OptionsSourceType { return this._type; }
|
||||
get variableNames(): { [index: string]: string; } { return this._variableNames; }
|
||||
|
||||
abstract getOptions(): Promise<string[] | CategoryValue[]>;
|
||||
getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
|
||||
throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName));
|
||||
}
|
||||
|
||||
getIsPassword(variableName: string): boolean {
|
||||
throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName));
|
||||
}
|
||||
|
||||
constructor(private _variableNames: { [index: string]: string }, private _type: OptionsSourceType) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that provides options sources for an Arc Data Controller
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class that provides options sources for an Arc Data Controller's Config Profiles
|
||||
*/
|
||||
export class ArcControllerConfigProfilesOptionsSource extends OptionsSource {
|
||||
async getOptions(): Promise<string[]> {
|
||||
return (await apiService.azdataApi.azdata.arc.dc.config.list()).result;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { IOptionsSourceProvider } from 'resource-deployment';
|
||||
import * as vscode from 'vscode';
|
||||
import { OptionsSourceType } from './helpers/optionSources';
|
||||
|
||||
export const NoteBookEnvironmentVariablePrefix = 'AZDATA_NB_VAR_';
|
||||
|
||||
@@ -219,11 +219,9 @@ export type ComponentCSSStyles = {
|
||||
};
|
||||
|
||||
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;
|
||||
provider?: IOptionsSourceProvider
|
||||
readonly variableNames?: { [index: string]: string; };
|
||||
readonly providerId: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { OptionsSourceType } from './helpers/optionSources';
|
||||
import { FieldType, OptionsType } from './interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -23,14 +22,11 @@ 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 optionsSourceAlreadyDefined = (optionsSourceId: string) => localize('optionsSource.alreadyDefined', "Options Source with id:{0} is already defined", optionsSourceId);
|
||||
export const noOptionsSourceDefined = (optionsSourceId: string) => localize('optionsSource.notDefined', "No Options Source defined for id: {0}", optionsSourceId);
|
||||
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");
|
||||
|
||||
@@ -12,10 +12,12 @@ import { ResourceTypeService } from './services/resourceTypeService';
|
||||
import { ToolsService } from './services/toolsService';
|
||||
import { DeploymentInputDialog } from './ui/deploymentInputDialog';
|
||||
import { ResourceTypePickerDialog } from './ui/resourceTypePickerDialog';
|
||||
import * as rd from 'resource-deployment';
|
||||
import { getExtensionApi } from './api';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<rd.IExtension> {
|
||||
const platformService = new PlatformService(context.globalStoragePath);
|
||||
await platformService.initialize();
|
||||
const toolsService = new ToolsService(platformService);
|
||||
@@ -27,7 +29,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
||||
const errorMessage = localize('resourceDeployment.FailedToLoadExtension', "Failed to load extension: {0}, Error detected in the resource type definition in package.json, check debug console for details.", context.extensionPath);
|
||||
vscode.window.showErrorMessage(errorMessage);
|
||||
validationFailures.forEach(message => console.error(message));
|
||||
return;
|
||||
return <any>undefined;
|
||||
}
|
||||
/**
|
||||
* Opens a new ResourceTypePickerDialog
|
||||
@@ -73,6 +75,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
||||
const dialog = new DeploymentInputDialog(notebookService, platformService, toolsService, dialogInfo);
|
||||
dialog.open();
|
||||
});
|
||||
return getExtensionApi();
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
|
||||
@@ -10,7 +10,7 @@ import { isString } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { NotebookPathInfo } from '../interfaces';
|
||||
import { getDateTimeString, getErrorMessage } from '../utils';
|
||||
import { getDateTimeString, getErrorMessage } from '../common/utils';
|
||||
import { IPlatformService } from './platformService';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as rd from 'resource-deployment';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
class OptionsSourcesService {
|
||||
private _optionsSourceStore = new Map<string, rd.IOptionsSourceProvider>();
|
||||
registerOptionsSourceProvider(provider: rd.IOptionsSourceProvider): void {
|
||||
if (this._optionsSourceStore.has(provider.optionsSourceId)) {
|
||||
throw new Error(loc.optionsSourceAlreadyDefined(provider.optionsSourceId));
|
||||
}
|
||||
this._optionsSourceStore.set(provider.optionsSourceId, provider);
|
||||
}
|
||||
|
||||
getOptionsSource(optionsSourceProviderId: string): rd.IOptionsSourceProvider {
|
||||
const optionsSource = this._optionsSourceStore.get(optionsSourceProviderId);
|
||||
if (optionsSource === undefined) {
|
||||
throw new Error(loc.noOptionsSourceDefined(optionsSourceProviderId));
|
||||
}
|
||||
return optionsSource;
|
||||
}
|
||||
}
|
||||
|
||||
export const optionsSourcesService = new OptionsSourcesService();
|
||||
@@ -10,7 +10,7 @@ import * as sudo from 'sudo-prompt';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { OsDistribution, OsRelease } from '../interfaces';
|
||||
import { getErrorMessage } from '../utils';
|
||||
import { getErrorMessage } from '../common/utils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const extensionOutputChannel = localize('resourceDeployment.outputChannel', "Deployments");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import { EOL } from 'os';
|
||||
import * as path from 'path';
|
||||
import { SemVer } from 'semver';
|
||||
@@ -9,10 +10,9 @@ import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { AzdataInstallLocationKey, DeploymentConfigurationKey } from '../../constants';
|
||||
import { Command, OsDistribution, ToolStatus, ToolType } from '../../interfaces';
|
||||
import { apiService } from '../apiService';
|
||||
import * as loc from '../../localizedConstants';
|
||||
import { IPlatformService } from '../platformService';
|
||||
import { dependencyType, ToolBase } from './toolBase';
|
||||
import * as loc from '../../localizedConstants';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
export const AzdataToolName = 'azdata';
|
||||
@@ -21,6 +21,7 @@ const macInstallationRoot = '/usr/local/bin';
|
||||
const debianInstallationRoot = '/usr/local/bin';
|
||||
|
||||
export class AzdataTool extends ToolBase {
|
||||
private azdataApi!: azdataExt.IExtension;
|
||||
constructor(platformService: IPlatformService) {
|
||||
super(platformService);
|
||||
}
|
||||
@@ -46,7 +47,7 @@ export class AzdataTool extends ToolBase {
|
||||
}
|
||||
|
||||
public isEulaAccepted(): boolean {
|
||||
if (apiService.azdataApi.isEulaAccepted()) {
|
||||
if (this.azdataApi.isEulaAccepted()) {
|
||||
return true;
|
||||
} else {
|
||||
this.setStatusDescription(loc.azdataEulaNotAccepted);
|
||||
@@ -55,7 +56,7 @@ export class AzdataTool extends ToolBase {
|
||||
}
|
||||
|
||||
public async promptForEula(): Promise<boolean> {
|
||||
const eulaAccepted = await apiService.azdataApi.promptForEula();
|
||||
const eulaAccepted = await this.azdataApi.promptForEula();
|
||||
if (!eulaAccepted) {
|
||||
this.setStatusDescription(loc.azdataEulaDeclined);
|
||||
}
|
||||
@@ -80,15 +81,16 @@ export class AzdataTool extends ToolBase {
|
||||
* updates the version and status for the tool.
|
||||
*/
|
||||
protected async updateVersionAndStatus(): Promise<void> {
|
||||
this.azdataApi = await vscode.extensions.getExtension(azdataExt.extension.name)?.activate();
|
||||
this.setStatusDescription('');
|
||||
await this.addInstallationSearchPathsToSystemPath();
|
||||
|
||||
const commandOutput = await apiService.azdataApi.azdata.version();
|
||||
this.version = apiService.azdataApi.azdata.getSemVersion();
|
||||
const commandOutput = await this.azdataApi.azdata.version();
|
||||
this.version = await this.azdataApi.azdata.getSemVersion();
|
||||
if (this.version) {
|
||||
if (this.autoInstallSupported) {
|
||||
// set the installationPath
|
||||
this.setInstallationPathOrAdditionalInformation(apiService.azdataApi.azdata.getPath());
|
||||
this.setInstallationPathOrAdditionalInformation(await this.azdataApi.azdata.getPath());
|
||||
}
|
||||
this.setStatus(ToolStatus.Installed);
|
||||
}
|
||||
@@ -99,8 +101,8 @@ export class AzdataTool extends ToolBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected getVersionFromOutput(output: string): SemVer | undefined {
|
||||
return apiService.azdataApi.azdata.getSemVersion();
|
||||
protected getVersionFromOutput(output: string): SemVer | Promise<SemVer> | undefined {
|
||||
return this.azdataApi.azdata.getSemVersion();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { EOL } from 'os';
|
||||
import * as path from 'path';
|
||||
import { SemVer, compare as SemVerCompare } from 'semver';
|
||||
import { compare as SemVerCompare, SemVer } from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { getErrorMessage } from '../../common/utils';
|
||||
import { Command, ITool, OsDistribution, ToolStatus, ToolType } from '../../interfaces';
|
||||
import { getErrorMessage } from '../../utils';
|
||||
import { IPlatformService } from '../platformService';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -52,7 +52,7 @@ export abstract class ToolBase implements ITool {
|
||||
protected abstract readonly allInstallationCommands: Map<OsDistribution, Command[]>;
|
||||
protected readonly dependenciesByOsType: Map<OsDistribution, dependencyType[]> = new Map<OsDistribution, dependencyType[]>();
|
||||
|
||||
protected abstract getVersionFromOutput(output: string): SemVer | undefined;
|
||||
protected abstract getVersionFromOutput(output: string): SemVer | Promise<SemVer> | undefined;
|
||||
protected readonly _onDidUpdateData = new vscode.EventEmitter<ITool>();
|
||||
protected readonly uninstallCommand?: string;
|
||||
|
||||
@@ -274,7 +274,7 @@ export abstract class ToolBase implements ITool {
|
||||
ignoreError: true
|
||||
},
|
||||
);
|
||||
this.version = this.getVersionFromOutput(commandOutput);
|
||||
this.version = await this.getVersionFromOutput(commandOutput);
|
||||
if (this.version) {
|
||||
if (this.autoInstallSupported) {
|
||||
// discover and set the installationPath
|
||||
|
||||
28
extensions/resource-deployment/src/typings/resource-deployment.d.ts
vendored
Normal file
28
extensions/resource-deployment/src/typings/resource-deployment.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
declare module 'resource-deployment' {
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
export const enum extension {
|
||||
name = 'Microsoft.resource-deployment'
|
||||
}
|
||||
export interface IOptionsSourceProvider {
|
||||
readonly optionsSourceId: string,
|
||||
getOptions(): Promise<string[] | azdata.CategoryValue[]> | string[] | azdata.CategoryValue[];
|
||||
getVariableValue?: (variableName: string, input: string) => Promise<string> | string;
|
||||
getIsPassword?: (variableName: string) => boolean | Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Covers defining what the resource-deployment extension exports to other extensions
|
||||
*
|
||||
* IMPORTANT: THIS IS NOT A HARD DEFINITION unlike vscode; therefore no enums or classes should be defined here
|
||||
* (const enums get evaluated when typescript -> javascript so those are fine)
|
||||
*/
|
||||
|
||||
export interface IExtension {
|
||||
registerOptionsSourceProvider(provider: IOptionsSourceProvider): void
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { IAzdataService } from '../../services/azdataService';
|
||||
import { IKubeService } from '../../services/kubeService';
|
||||
import { INotebookService } from '../../services/notebookService';
|
||||
import { IToolsService } from '../../services/toolsService';
|
||||
import { getErrorMessage } from '../../utils';
|
||||
import { getErrorMessage } from '../../common/utils';
|
||||
import { InputComponents } from '../modelViewUtils';
|
||||
import { WizardBase } from '../wizardBase';
|
||||
import { WizardPageBase } from '../wizardPageBase';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { delimiter } from 'path';
|
||||
import { BdcDeploymentType, ITool } from '../../interfaces';
|
||||
import { BigDataClusterDeploymentProfile, DataResource, HdfsResource, SqlServerMasterResource } from '../../services/bigDataClusterDeploymentProfile';
|
||||
import { KubeCtlToolName } from '../../services/tools/kubeCtlTool';
|
||||
import { getRuntimeBinaryPathEnvironmentVariableName, setEnvironmentVariablesForInstallPaths } from '../../utils';
|
||||
import { getRuntimeBinaryPathEnvironmentVariableName, setEnvironmentVariablesForInstallPaths } from '../../common/utils';
|
||||
import { Model } from '../model';
|
||||
import { ToolsInstallPath } from './../../constants';
|
||||
import * as VariableNames from './constants';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { EOL } from 'os';
|
||||
import { ITool, NoteBookEnvironmentVariablePrefix } from '../interfaces';
|
||||
import { setEnvironmentVariablesForInstallPaths, getRuntimeBinaryPathEnvironmentVariableName } from '../utils';
|
||||
import { setEnvironmentVariablesForInstallPaths, getRuntimeBinaryPathEnvironmentVariableName } from '../common/utils';
|
||||
import { ToolsInstallPath } from '../constants';
|
||||
import { delimiter } from 'path';
|
||||
|
||||
|
||||
@@ -9,17 +9,18 @@ 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 { ArcControllerConfigProfilesOptionsSource, 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 { KubeCtlTool, KubeCtlToolName } from '../services/tools/kubeCtlTool';
|
||||
import { IToolsService } from '../services/toolsService';
|
||||
import { getDateTimeString, getErrorMessage, throwUnless } from '../utils';
|
||||
import { getDateTimeString, getErrorMessage, throwUnless } from '../common/utils';
|
||||
import { WizardInfoBase } from './../interfaces';
|
||||
import { Model } from './model';
|
||||
import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder';
|
||||
import { optionsSourcesService } from '../services/optionSourcesService';
|
||||
import { IOptionsSourceProvider } from 'resource-deployment';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -432,21 +433,10 @@ async function processOptionsTypeField(context: FieldContext): Promise<void> {
|
||||
}
|
||||
throwUnless(typeof context.fieldInfo.options === 'object', loc.optionsNotObjectOrArray);
|
||||
throwUnless('optionsType' in context.fieldInfo.options, loc.optionsTypeNotFound);
|
||||
if (context.fieldInfo.options.source) {
|
||||
if (context.fieldInfo.options.source?.providerId) {
|
||||
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;
|
||||
case OptionsSourceType.ArcControllerConfigProfilesOptionsSource:
|
||||
optionsSource = new ArcControllerConfigProfilesOptionsSource(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();
|
||||
context.fieldInfo.options.source.provider = optionsSourcesService.getOptionsSource(context.fieldInfo.options.source.providerId);
|
||||
context.fieldInfo.options.values = await context.fieldInfo.options.source.provider.getOptions();
|
||||
}
|
||||
catch (e) {
|
||||
disableControlButtons(context.container);
|
||||
@@ -466,35 +456,39 @@ async function processOptionsTypeField(context: FieldContext): Promise<void> {
|
||||
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)
|
||||
});
|
||||
}
|
||||
const optionsSource = context.fieldInfo.options.source;
|
||||
if (optionsSource?.provider) {
|
||||
const optionsSourceProvider = optionsSource.provider;
|
||||
await Promise.all(Object.keys(context.fieldInfo.options.source?.variableNames ?? {}).map(async key => {
|
||||
await configureOptionsSourceSubfields(context, optionsSource, key, optionsComponent, optionsSourceProvider);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async function configureOptionsSourceSubfields(context: FieldContext, optionsSource: IOptionsSource, variableKey: string, optionsComponent: InputComponent, optionsSourceProvider: IOptionsSourceProvider) {
|
||||
context.fieldInfo.subFields!.push({
|
||||
label: context.fieldInfo.label,
|
||||
variableName: optionsSource.variableNames![variableKey]
|
||||
});
|
||||
context.onNewInputComponentCreated(optionsSource.variableNames![variableKey], {
|
||||
component: optionsComponent,
|
||||
inputValueTransformer: async (optionValue: string) => {
|
||||
try {
|
||||
return await optionsSourceProvider.getVariableValue!(variableKey, optionValue);
|
||||
} catch (e) {
|
||||
disableControlButtons(context.container);
|
||||
context.container.message = {
|
||||
text: getErrorMessage(e),
|
||||
description: '',
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
return '';
|
||||
}
|
||||
},
|
||||
isPassword: await optionsSourceProvider.getIsPassword!(variableKey)
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1304,7 +1298,7 @@ async function getInputComponentValue(inputComponents: InputComponents, key: str
|
||||
if (input === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
let value;
|
||||
let value: string | undefined;
|
||||
if (input instanceof RadioGroupLoadingComponentBuilder) {
|
||||
value = input.value;
|
||||
} else if ('checked' in input) { // CheckBoxComponent
|
||||
@@ -1319,14 +1313,7 @@ async function getInputComponentValue(inputComponents: InputComponents, key: str
|
||||
} else {
|
||||
throw new Error(`Unknown input type with ID ${input.id}`);
|
||||
}
|
||||
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
||||
if (inputValueTransformer) {
|
||||
value = inputValueTransformer(value ?? '');
|
||||
if (typeof value !== 'string') {
|
||||
value = await value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
return inputComponents[key].inputValueTransformer ? await inputComponents[key].inputValueTransformer!(value ?? '') : value;
|
||||
}
|
||||
|
||||
export function isInputBoxEmpty(input: azdata.InputBoxComponent): boolean {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { OptionsInfo, FieldInfo } from '../interfaces';
|
||||
import { getErrorMessage } from '../utils';
|
||||
import { getErrorMessage } from '../common/utils';
|
||||
|
||||
export class RadioGroupLoadingComponentBuilder implements azdata.ComponentBuilder<azdata.LoadingComponent, azdata.LoadingComponentProperties> {
|
||||
private _optionsDivContainer!: azdata.DivContainer;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AgreementInfo, DeploymentProvider, ITool, ResourceType, ToolStatus } fr
|
||||
import { select } from '../localizedConstants';
|
||||
import { IResourceTypeService } from '../services/resourceTypeService';
|
||||
import { IToolsService } from '../services/toolsService';
|
||||
import { getErrorMessage } from '../utils';
|
||||
import { getErrorMessage } from '../common/utils';
|
||||
import * as loc from './../localizedConstants';
|
||||
import { DialogBase } from './dialogBase';
|
||||
import { createFlexContainer } from './modelViewUtils';
|
||||
|
||||
Reference in New Issue
Block a user