Co-authored-by: Arvind Ranasaria <ranasaria@outlook.com>
This commit is contained in:
Charles Gagnon
2020-10-07 20:36:51 -07:00
committed by GitHub
parent 93e806cca1
commit 8e8d9b5f59
37 changed files with 540 additions and 365 deletions

View File

@@ -17,7 +17,8 @@
"onView:azureArc"
],
"extensionDependencies": [
"Microsoft.azdata"
"Microsoft.azdata",
"Microsoft.resource-deployment"
],
"repository": {
"type": "git",
@@ -208,7 +209,7 @@
"editable": false,
"options": {
"source": {
"type": "ArcControllerConfigProfilesOptionsSource"
"providerId": "arc.controller.config.profiles"
},
"defaultValue": "azure-arc-aks-default-storage",
"optionsType": "radio"
@@ -588,7 +589,7 @@
"required": true,
"options": {
"source": {
"type": "ArcControllersOptionsSource",
"providerId": "arc.controllers",
"variableNames": {
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
@@ -710,7 +711,7 @@
"required": true,
"options": {
"source": {
"type": "ArcControllersOptionsSource",
"providerId": "arc.controllers",
"variableNames": {
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",

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

@@ -225,3 +225,21 @@ export function parseIpAndPort(address: string): { ip: string, port: string } {
port: sections[1]
};
}
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);
}
}

View File

@@ -4,11 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as arc from 'arc';
import * as rd from 'resource-deployment';
import * as vscode from 'vscode';
import { UserCancelledError } from './common/utils';
import { arcApi } from './common/api';
import { IconPathHelper, refreshActionId } from './constants';
import * as loc from './localizedConstants';
import { ConnectToControllerDialog, PasswordToControllerDialog } from './ui/dialogs/connectControllerDialog';
import { ArcControllersOptionsSourceProvider } from './providers/arcControllersOptionsSourceProvider';
import { ConnectToControllerDialog } from './ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from './ui/tree/azureArcTreeDataProvider';
import { ControllerTreeNode } from './ui/tree/controllerTreeNode';
import { TreeNode } from './ui/tree/treeNode';
@@ -55,41 +57,12 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
}
});
await checkArcDeploymentExtension();
// register option sources
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider));
return {
getRegisteredDataControllers: async () => (await treeDataProvider.getChildren())
.filter(node => node instanceof ControllerTreeNode)
.map(node => ({
label: (node as ControllerTreeNode).model.label,
info: (node as ControllerTreeNode).model.info
})),
getControllerPassword: async (controllerInfo: arc.ControllerInfo) => {
return await treeDataProvider.getPassword(controllerInfo);
},
reacquireControllerPassword: async (controllerInfo: arc.ControllerInfo) => {
let model;
const dialog = new PasswordToControllerDialog(treeDataProvider);
dialog.showDialog(controllerInfo);
model = await dialog.waitForClose();
if (!model) {
throw new UserCancelledError();
}
return model.password;
}
};
return arcApi(treeDataProvider);
}
export function deactivate(): void {
}
async function checkArcDeploymentExtension(): Promise<void> {
const version = vscode.extensions.getExtension('Microsoft.arcdeployment')?.packageJSON.version;
if (version && version !== '0.3.2') {
// If we have an older version of the deployment extension installed then uninstall it now since it's replaced
// by this extension. (the latest version of the Arc Deployment extension will uninstall itself so don't do
// anything here if that's already updated)
await vscode.commands.executeCommand('workbench.extensions.uninstallExtension', 'Microsoft.arcdeployment');
vscode.window.showInformationMessage(loc.arcDeploymentDeprecation);
}
}

View File

@@ -161,3 +161,9 @@ export function errorConnectingToController(error: any): string { return localiz
export function passwordAcquisitionFailed(error: any): string { return localize('arc.passwordAcquisitionFailed', "Failed to acquire password. {0}", getErrorMessage(error)); }
export const invalidPassword = localize('arc.invalidPassword', "The password did not work, try again.");
export function errorVerifyingPassword(error: any): string { return localize('arc.errorVerifyingPassword', "Error encountered while verifying password. {0}", getErrorMessage(error)); }
export const onlyOneControllerSupported = localize('arc.onlyOneControllerSupported', "Only one controller connection is currently supported at this time. Do you wish to remove the existing connection and add a new one?");
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 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 noControllerInfoFound = (name: string) => localize('noControllerInfoFound', "Controller Info 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);

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* 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 azdata from 'azdata';
import * as rd from 'resource-deployment';
import { getControllerPassword, getRegisteredDataControllers, reacquireControllerPassword } from '../common/api';
import { CacheManager } from '../common/cacheManager';
import { throwUnless } from '../common/utils';
import * as loc from '../localizedConstants';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
/**
* Class that provides options sources for an Arc Data Controller
*/
export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourceProvider {
private _cacheManager = new CacheManager<string, string>();
readonly optionsSourceId = 'arc.controllers';
constructor(private _treeProvider: AzureArcTreeDataProvider) { }
async getOptions(): Promise<string[] | azdata.CategoryValue[]> {
const controllers = await getRegisteredDataControllers(this._treeProvider);
throwUnless(controllers !== undefined && controllers.length !== 0, loc.noControllersConnected);
return controllers.map(ci => {
return ci.label;
});
}
private async retrieveVariable(key: string): Promise<string> {
const [variableName, controllerLabel] = JSON.parse(key);
const controller = (await getRegisteredDataControllers(this._treeProvider)).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': return this.getPassword(controller);
default: throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName));
}
}
getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
return this._cacheManager.getCacheEntry(JSON.stringify([variableName, controllerLabel]), this.retrieveVariable);
}
private async getPassword(controller: arc.DataController): Promise<string> {
let password = await getControllerPassword(this._treeProvider, controller.info);
if (!password) {
password = await reacquireControllerPassword(this._treeProvider, controller.info);
}
throwUnless(password !== undefined, loc.noPasswordFound(controller.label));
return password;
}
getIsPassword(variableName: string): boolean {
switch (variableName) {
case 'endpoint': return false;
case 'username': return false;
case 'password': return true;
default: throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName));
}
}
}

View File

@@ -8,3 +8,4 @@
/// <reference path='../../../azurecore/src/azurecore.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../azdata/src/typings/azdata-ext.d.ts'/>
/// <reference path='../../../resource-deployment/src/typings/resource-deployment.d.ts'/>

View File

@@ -18,6 +18,9 @@
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"extensionDependencies": [
"microsoft.resource-deployment"
],
"main": "./out/extension",
"contributes": {
"configuration": [

View File

@@ -0,0 +1,128 @@
/*---------------------------------------------------------------------------------------------
* 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 * as vscode from 'vscode';
import { IAzdataTool, isEulaAccepted, promptForEula } from './azdata';
import Logger from './common/logger';
import { NoAzdataError } from './common/utils';
import * as constants from './constants';
import * as loc from './localizedConstants';
import { AzdataToolService } from './services/azdataToolService';
function throwIfNoAzdataOrEulaNotAccepted(azdata: IAzdataTool | undefined, eulaAccepted: boolean): asserts azdata {
throwIfNoAzdata(azdata);
if (!eulaAccepted) {
Logger.log(loc.eulaNotAccepted);
throw new Error(loc.eulaNotAccepted);
}
}
export function throwIfNoAzdata(localAzdata: IAzdataTool | undefined): asserts localAzdata {
if (!localAzdata) {
Logger.log(loc.noAzdata);
throw new NoAzdataError();
}
}
export function getExtensionApi(memento: vscode.Memento, azdataToolService: AzdataToolService, localAzdataDiscovered: Promise<IAzdataTool | undefined>): azdataExt.IExtension {
return {
isEulaAccepted: async () => {
throwIfNoAzdata(await localAzdataDiscovered); // ensure that we have discovered Azdata
return !!memento.get<boolean>(constants.eulaAccepted);
},
promptForEula: async (requireUserAction: boolean = true): Promise<boolean> => {
await localAzdataDiscovered;
return promptForEula(memento, true /* userRequested */, requireUserAction);
},
azdata: getAzdataApi(localAzdataDiscovered, azdataToolService, memento)
};
}
export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefined>, azdataToolService: AzdataToolService, memento: vscode.Memento): azdataExt.IAzdataApi {
return {
arc: {
dc: {
create: async (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string) => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass);
},
endpoint: {
list: async () => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.dc.endpoint.list();
}
},
config: {
list: async () => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.dc.config.list();
},
show: async () => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.dc.config.show();
}
}
},
postgres: {
server: {
list: async () => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.list();
},
show: async (name: string) => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.show(name);
}
}
},
sql: {
mi: {
delete: async (name: string) => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.sql.mi.delete(name);
},
list: async () => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.sql.mi.list();
},
show: async (name: string) => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.sql.mi.show(name);
}
}
}
},
getPath: async () => {
await localAzdataDiscovered;
throwIfNoAzdata(azdataToolService.localAzdata);
return azdataToolService.localAzdata.getPath();
},
login: async (endpoint: string, username: string, password: string) => {
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.login(endpoint, username, password);
},
getSemVersion: async () => {
await localAzdataDiscovered;
throwIfNoAzdata(azdataToolService.localAzdata);
return azdataToolService.localAzdata.getSemVersion();
},
version: async () => {
await localAzdataDiscovered;
throwIfNoAzdata(azdataToolService.localAzdata);
return azdataToolService.localAzdata.version();
}
};
}

View File

@@ -35,7 +35,7 @@ export interface IAzdataTool extends azdataExt.IAzdataApi {
/**
* An object to interact with the azdata tool installed on the box.
*/
export class AzdataTool implements IAzdataTool {
export class AzdataTool implements azdataExt.IAzdataApi {
private _semVersion: SemVer;
constructor(private _path: string, version: string) {
@@ -47,14 +47,14 @@ export class AzdataTool implements IAzdataTool {
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
* Azdata has gotten reinstalled in the background after this IAzdataApi object was constructed.
*/
public getSemVersion() {
public async getSemVersion(): Promise<SemVer> {
return this._semVersion;
}
/**
* gets the path where azdata tool is installed
*/
public getPath() {
public async getPath(): Promise<string> {
return this._path;
}
@@ -190,7 +190,7 @@ export async function findAzdata(): Promise<IAzdataTool> {
try {
const azdata = await findSpecificAzdata();
await vscode.commands.executeCommand('setContext', azdataFound, true); // save a context key that azdata was found so that command for installing azdata is no longer available in commandPalette and that for updating it is.
Logger.log(loc.foundExistingAzdata(azdata.getPath(), azdata.getSemVersion().raw));
Logger.log(loc.foundExistingAzdata(await azdata.getPath(), (await azdata.getSemVersion()).raw));
return azdata;
} catch (err) {
Logger.log(loc.couldNotFindAzdata(err));
@@ -277,11 +277,11 @@ export async function checkAndInstallAzdata(userRequested: boolean = false): Pro
export async function checkAndUpdateAzdata(currentAzdata?: IAzdataTool, userRequested: boolean = false): Promise<boolean> {
if (currentAzdata !== undefined) {
const newSemVersion = await discoverLatestAvailableAzdataVersion();
if (newSemVersion.compare(currentAzdata.getSemVersion()) === 1) {
Logger.log(loc.foundAzdataVersionToUpdateTo(newSemVersion.raw, currentAzdata.getSemVersion().raw));
if (newSemVersion.compare(await currentAzdata.getSemVersion()) === 1) {
Logger.log(loc.foundAzdataVersionToUpdateTo(newSemVersion.raw, (await currentAzdata.getSemVersion()).raw));
return await promptToUpdateAzdata(newSemVersion.raw, userRequested);
} else {
Logger.log(loc.currentlyInstalledVersionIsLatest(currentAzdata.getSemVersion().raw));
Logger.log(loc.currentlyInstalledVersionIsLatest((await currentAzdata.getSemVersion()).raw));
}
} else {
Logger.log(loc.updateCheckSkipped);
@@ -376,6 +376,17 @@ async function promptToUpdateAzdata(newVersion: string, userRequested: boolean =
return false;
}
/**
* Returns true if Eula has been accepted.
*
* @param memento The memento that stores the eulaAccepted state
*/
export function isEulaAccepted(memento: vscode.Memento): boolean {
return !!memento.get<boolean>(eulaAccepted);
}
/**
* Prompts user to accept EULA. Stores and returns the user response to EULA prompt.
* @param memento - memento where the user response is stored.

View File

@@ -3,7 +3,20 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as which from 'which';
import * as loc from '../localizedConstants';
export class NoAzdataError extends Error implements azdataExt.ErrorWithLink {
constructor() {
super(loc.noAzdata);
}
public get messageWithLink(): string {
return loc.noAzdataWithLink;
}
}
/**
* Searches for the first instance of the specified executable in the PATH environment variable

View File

@@ -4,31 +4,34 @@
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as rd from 'resource-deployment';
import * as vscode from 'vscode';
import { checkAndInstallAzdata, checkAndUpdateAzdata, findAzdata, IAzdataTool, promptForEula } from './azdata';
import { getExtensionApi } from './api';
import { checkAndInstallAzdata, checkAndUpdateAzdata, findAzdata, isEulaAccepted, promptForEula } from './azdata';
import Logger from './common/logger';
import * as constants from './constants';
import * as loc from './localizedConstants';
import { ArcControllerConfigProfilesOptionsSource } from './providers/arcControllerConfigProfilesOptionsSource';
import { AzdataToolService } from './services/azdataToolService';
let localAzdata: IAzdataTool | undefined = undefined;
let eulaAccepted: boolean = false;
export async function activate(context: vscode.ExtensionContext): Promise<azdataExt.IExtension> {
const azdataToolService = new AzdataToolService();
let eulaAccepted: boolean = false;
vscode.commands.registerCommand('azdata.acceptEula', async () => {
eulaAccepted = await promptForEula(context.globalState, true /* userRequested */);
await promptForEula(context.globalState, true /* userRequested */);
});
vscode.commands.registerCommand('azdata.install', async () => {
localAzdata = await checkAndInstallAzdata(true /* userRequested */);
azdataToolService.localAzdata = await checkAndInstallAzdata(true /* userRequested */);
});
vscode.commands.registerCommand('azdata.update', async () => {
if (await checkAndUpdateAzdata(localAzdata, true /* userRequested */)) { // if an update was performed
localAzdata = await findAzdata(); // find and save the currently installed azdata
if (await checkAndUpdateAzdata(azdataToolService.localAzdata, true /* userRequested */)) { // if an update was performed
azdataToolService.localAzdata = await findAzdata(); // find and save the currently installed azdata
}
});
eulaAccepted = !!context.globalState.get<boolean>(constants.eulaAccepted); // fetch eula acceptance state from memento
eulaAccepted = isEulaAccepted(context.globalState); // fetch eula acceptance state from memento
await vscode.commands.executeCommand('setContext', constants.eulaAccepted, eulaAccepted); // set a context key for current value of eulaAccepted state retrieved from memento so that command for accepting eula is available/unavailable in commandPalette appropriately.
Logger.log(loc.eulaAcceptedStateOnStartup(eulaAccepted));
if (!eulaAccepted) {
@@ -42,110 +45,38 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
}
// Don't block on this since we want the extension to finish activating without needing user input
checkAndInstallAzdata() // install if not installed and user wants it.
const localAzdataDiscovered = checkAndInstallAzdata() // install if not installed and user wants it.
.then(async azdataTool => {
localAzdata = azdataTool;
if (localAzdata !== undefined) {
if (azdataTool !== undefined) {
azdataToolService.localAzdata = azdataTool;
if (!eulaAccepted) {
// Don't block on this since we want extension to finish activating without requiring user actions.
// If EULA has not been accepted then we will check again while executing azdata commands.
promptForEula(context.globalState)
.then(async (userResponse: boolean) => {
eulaAccepted = userResponse;
})
.catch((err) => console.log(err));
}
try {
//update if available and user wants it.
if (await checkAndUpdateAzdata(localAzdata)) { // if an update was performed
localAzdata = await findAzdata(); // find and save the currently installed azdata
if (await checkAndUpdateAzdata(azdataToolService.localAzdata)) { // if an update was performed
azdataToolService.localAzdata = await findAzdata(); // find and save the currently installed azdata
}
} catch (err) {
vscode.window.showWarningMessage(loc.updateError(err));
}
}
return azdataTool;
});
return {
isEulaAccepted: () => !!context.globalState.get<boolean>(constants.eulaAccepted),
promptForEula: (onError: boolean = true): Promise<boolean> => promptForEula(context.globalState, true /* userRequested */, onError),
azdata: {
arc: {
dc: {
create: async (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string) => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass);
},
endpoint: {
list: async () => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.dc.endpoint.list();
}
},
config: {
list: async () => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.dc.config.list();
},
show: async () => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.dc.config.show();
}
}
},
postgres: {
server: {
list: async () => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.postgres.server.list();
},
show: async (name: string) => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.postgres.server.show(name);
}
}
},
sql: {
mi: {
delete: async (name: string) => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.sql.mi.delete(name);
},
list: async () => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.sql.mi.list();
},
show: async (name: string) => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.sql.mi.show(name);
}
}
}
},
getPath: () => {
throwIfNoAzdata();
return localAzdata!.getPath();
},
login: async (endpoint: string, username: string, password: string) => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.login(endpoint, username, password);
},
getSemVersion: () => {
throwIfNoAzdata();
return localAzdata!.getSemVersion();
},
version: async () => {
throwIfNoAzdata();
return localAzdata!.version();
}
}
};
}
const azdataApi = getExtensionApi(context.globalState, azdataToolService, localAzdataDiscovered);
function throwIfNoAzdataOrEulaNotAccepted(): void {
throwIfNoAzdata();
if (!eulaAccepted) {
Logger.log(loc.eulaNotAccepted);
throw new Error(loc.eulaNotAccepted);
}
}
// register option source(s)
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi));
function throwIfNoAzdata() {
if (!localAzdata) {
Logger.log(loc.noAzdata);
throw new Error(loc.noAzdata);
}
return azdataApi;
}
export function deactivate(): void { }

View File

@@ -45,6 +45,7 @@ export const platformUnsupported = (platform: string): string => localize('azdat
export const unexpectedCommandError = (errMsg: string): string => localize('azdata.unexpectedCommandError', "Unexpected error executing command: {0}", errMsg);
export const unexpectedExitCode = (code: number, err: string): string => localize('azdata.unexpectedExitCode', "Unexpected exit code from command: {1} ({0})", code, err);
export const noAzdata = localize('azdata.NoAzdata', "No Azure Data CLI is available, [install the Azure Data CLI](command:azdata.install) to enable the features that require it.");
export const noAzdataWithLink = localize('azdata.noAzdataWithLink', "No Azure Data CLI is available, [install the Azure Data CLI](command:azdata.install) to enable the features that require it.");
export const skipInstall = (config: string): string => localize('azdata.skipInstall', "Skipping installation of azdata, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataInstallKey, config);
export const skipUpdate = (config: string): string => localize('azdata.skipUpdate', "Skipping update of azdata, since the operation was not user requested and config option: {0}.{1} is {2}", azdataConfigSection, azdataUpdateKey, config);

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* 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 azdataExt from 'azdata-ext';
/**
* Class that provides options sources for an Arc Data Controller
*/
export class ArcControllerConfigProfilesOptionsSource implements rd.IOptionsSourceProvider {
readonly optionsSourceId = 'arc.controller.config.profiles';
constructor(private _azdataExtApi: azdataExt.IExtension) { }
async getOptions(): Promise<string[]> {
if (!this._azdataExtApi.isEulaAccepted()) { // if eula has not yet be accepted then give user a chance to accept it
await this._azdataExtApi.promptForEula();
}
return (await this._azdataExtApi.azdata.arc.dc.config.list()).result;
}
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAzdataTool } from '../azdata';
export class AzdataToolService {
private _localAzdata: IAzdataTool | undefined;
constructor() {
}
/**
* Gets the localAzdata that was last saved
*/
get localAzdata(): IAzdataTool | undefined {
return this._localAzdata;
}
/**
* Sets the localAzdata that was last saved
*
* @param memento The memento that stores the localAzdata object
*/
set localAzdata(azdata: IAzdataTool | undefined) {
this._localAzdata = azdata;
}
}

View File

@@ -16,6 +16,10 @@ declare module 'azdata-ext' {
name = 'Microsoft.azdata'
}
export interface ErrorWithLink extends Error {
messageWithLink: string;
}
export interface DcEndpointListResult {
description: string, // "Management Proxy"
endpoint: string, // "https://10.91.86.39:30777"
@@ -227,14 +231,14 @@ declare module 'azdata-ext' {
}
}
},
getPath(): string,
getPath(): Promise<string>,
login(endpoint: string, username: string, password: string): Promise<AzdataOutput<any>>,
/**
* The semVersion corresponding to this installation of azdata. version() method should have been run
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
* Azdata has gotten reinstalled in the background after this IAzdataApi object was constructed.
*/
getSemVersion(): SemVer,
getSemVersion(): Promise<SemVer>,
version(): Promise<AzdataOutput<string>>
}
@@ -244,7 +248,7 @@ declare module 'azdata-ext' {
/**
* returns true if AZDATA CLI EULA has been previously accepted by the user.
*/
isEulaAccepted(): boolean;
isEulaAccepted(): Promise<boolean>;
/**
* Prompts user to accept EULA. Stores and returns the user response to EULA prompt.
@@ -253,6 +257,7 @@ declare module 'azdata-ext' {
* pre-requisite, the calling code has to ensure that the EULA has not yet been previously accepted by the user. The code can use @see isEulaAccepted() call to ascertain this.
* returns true if the user accepted the EULA.
*/
promptForEula(requireUserAction?: boolean): Promise<boolean>
promptForEula(requireUserAction?: boolean): Promise<boolean>;
}
}

View File

@@ -6,3 +6,4 @@
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../resource-deployment/src/typings/resource-deployment.d.ts'/>

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

View File

@@ -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)

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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_';
@@ -208,11 +208,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;
}

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
import { OptionsSourceType } from './helpers/optionSources';
import { ResourceTypeCategories } from './constants';
import { FieldType, OptionsType } from './interfaces';
@@ -24,14 +23,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");

View File

@@ -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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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");

View File

@@ -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();
}

View File

@@ -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

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

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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;

View File

@@ -9,7 +9,7 @@ import * as nls from 'vscode-nls';
import { AgreementInfo, DeploymentProvider, ITool, ResourceType, ToolStatus } from '../interfaces';
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';