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:
Arvind Ranasaria
2020-10-05 19:29:40 -07:00
committed by GitHub
parent 362605cea0
commit c679d5e1f0
35 changed files with 534 additions and 377 deletions

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* 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 { PasswordToControllerDialog } from '../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
import { ControllerTreeNode } from '../ui/tree/controllerTreeNode';
import { UserCancelledError } from './utils';
export function arcApi(treeDataProvider: AzureArcTreeDataProvider): arc.IExtension {
return {
getRegisteredDataControllers: () => getRegisteredDataControllers(treeDataProvider),
getControllerPassword: (controllerInfo: arc.ControllerInfo) => getControllerPassword(treeDataProvider, controllerInfo),
reacquireControllerPassword: (controllerInfo: arc.ControllerInfo) => reacquireControllerPassword(treeDataProvider, controllerInfo)
};
}
export async function reacquireControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> {
const dialog = new PasswordToControllerDialog(treeDataProvider);
dialog.showDialog(controllerInfo);
const model = await dialog.waitForClose();
if (!model) {
throw new UserCancelledError();
}
return model.password;
}
export async function getControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> {
return await treeDataProvider.getPassword(controllerInfo);
}
export async function getRegisteredDataControllers(treeDataProvider: AzureArcTreeDataProvider): Promise<arc.DataController[]> {
return (await treeDataProvider.getChildren())
.filter(node => node instanceof ControllerTreeNode)
.map(node => ({
label: (node as ControllerTreeNode).model.label,
info: (node as ControllerTreeNode).model.info
}));
}

View 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!;
}
}
}
}
}
}

View File

@@ -216,3 +216,17 @@ export function parseIpAndPort(address: string): { ip: string, port: string } {
export function createCredentialId(controllerId: string, resourceType: string, instanceName: string): string {
return `${controllerId}::${resourceType}::${instanceName}`;
}
/**
* 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);
}
}