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,153 @@
/*---------------------------------------------------------------------------------------------
* 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: {
delete: async (name: string) => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.delete(name);
},
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);
},
edit: async (
name: string,
args: {
adminPassword?: boolean;
coresLimit?: string;
coresRequest?: string;
engineSettings?: string;
extensions?: string;
memoryLimit?: string;
memoryRequest?: string;
noWait?: boolean;
port?: number;
replaceEngineSettings?: boolean;
workers?: number;
},
additionalEnvVars?: { [key: string]: string; }) => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.edit(name, args, additionalEnvVars);
}
}
},
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

@@ -37,7 +37,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) {
@@ -49,14 +49,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;
}
@@ -225,7 +225,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));
@@ -312,11 +312,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);
@@ -413,6 +413,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

@@ -4,40 +4,42 @@
*--------------------------------------------------------------------------------------------*/
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 { NoAzdataError } from './common/utils';
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));
// 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.
@@ -49,127 +51,23 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
}
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: {
delete: async (name: string) => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.postgres.server.delete(name);
},
list: async () => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.postgres.server.list();
},
show: async (name: string) => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.postgres.server.show(name);
},
edit: async (
name: string,
args: {
adminPassword?: boolean,
coresLimit?: string,
coresRequest?: string,
engineSettings?: string,
extensions?: string,
memoryLimit?: string,
memoryRequest?: string,
noWait?: boolean,
port?: number,
replaceEngineSettings?: boolean,
workers?: number
},
additionalEnvVars?: { [key: string]: string }) => {
throwIfNoAzdataOrEulaNotAccepted();
return localAzdata!.arc.postgres.server.edit(name, args, additionalEnvVars);
}
}
},
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 NoAzdataError();
}
return azdataApi;
}
export function deactivate(): void { }

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

@@ -267,14 +267,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>>
}
@@ -284,7 +284,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.
@@ -293,6 +293,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'/>