Add extension api for calling azdata commands (#11763)

* Add extension api for calling azdata commands

* Update typings file name
This commit is contained in:
Charles Gagnon
2020-08-12 12:57:59 -07:00
committed by GitHub
parent d96fe82fbc
commit 094ee7c50c
4 changed files with 151 additions and 18 deletions

View File

@@ -9,15 +9,34 @@ import { HttpClient } from './common/httpClient';
import * as loc from './localizedConstants';
import { executeCommand, executeSudoCommand } from './common/childProcess';
import { searchForCmd } from './common/utils';
import { AzdataOutput } from './typings/azdata-ext';
export const azdataHostname = 'https://aka.ms';
export const azdataUri = 'azdata-msi';
/**
* Information about an azdata installation
*/
export interface IAzdata {
export interface IAzdataTool {
path: string,
version: string
version: string,
/**
* Executes azdata with the specified arguments (e.g. --version) and returns the result
* @param args The args to pass to azdata
* @param parseResult A function used to parse out the raw result into the desired shape
*/
executeCommand<R>(args: string[], parseResult: (result: any) => R[]): Promise<AzdataOutput<R>>
}
class AzdataTool implements IAzdataTool {
constructor(public path: string, public version: string, private _outputChannel: vscode.OutputChannel) { }
public async executeCommand<R>(args: string[], parseResult: (result: any) => R[]): Promise<AzdataOutput<R>> {
const output = JSON.parse((await executeCommand(`"${this.path}"`, args.concat(['--output', 'json']), this._outputChannel)).stdout);
return {
logs: <string[]>output.log,
stdout: <string[]>output.stdout,
stderr: <string[]>output.stderr,
result: parseResult(output.result)
};
}
}
/**
@@ -25,10 +44,10 @@ export interface IAzdata {
* or encountered an unexpected error.
* @param outputChannel Channel used to display diagnostic information
*/
export async function findAzdata(outputChannel: vscode.OutputChannel): Promise<IAzdata> {
export async function findAzdata(outputChannel: vscode.OutputChannel): Promise<IAzdataTool> {
outputChannel.appendLine(loc.searchingForAzdata);
try {
let azdata: IAzdata | undefined = undefined;
let azdata: IAzdataTool | undefined = undefined;
switch (process.platform) {
case 'win32':
azdata = await findAzdataWin32(outputChannel);
@@ -112,7 +131,7 @@ async function installAzdataLinux(outputChannel: vscode.OutputChannel): Promise<
* Finds azdata specifically on Windows
* @param outputChannel Channel used to display diagnostic information
*/
async function findAzdataWin32(outputChannel: vscode.OutputChannel): Promise<IAzdata> {
async function findAzdataWin32(outputChannel: vscode.OutputChannel): Promise<IAzdataTool> {
const promise = searchForCmd('azdata.cmd');
return findSpecificAzdata(await promise, outputChannel);
}
@@ -122,12 +141,9 @@ async function findAzdataWin32(outputChannel: vscode.OutputChannel): Promise<IAz
* @param path The path to the azdata executable
* @param outputChannel Channel used to display diagnostic information
*/
async function findSpecificAzdata(path: string, outputChannel: vscode.OutputChannel): Promise<IAzdata> {
async function findSpecificAzdata(path: string, outputChannel: vscode.OutputChannel): Promise<IAzdataTool> {
const versionOutput = await executeCommand(path, ['--version'], outputChannel);
return {
path: path,
version: parseVersion(versionOutput.stdout)
};
return new AzdataTool(path, parseVersion(versionOutput.stdout), outputChannel);
}
/**

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PostgresServerListResult, SqlInstanceListResult } from '../typings/azdata-ext';
/**
* Helper function to parse the raw output from the `azdata postgres server list` command
* @param result The raw JSON result array
*/
export function parsePostgresServerListResult(result: any[]): PostgresServerListResult[] {
return result.map(r => {
return {
id: r['ID'],
clusterIP: r['clusterIP'],
externalIP: r['externalIP'],
mustRestart: r['mustRestart'],
name: r['name'],
status: r['status']
};
});
}
/**
* Helper function to parse the raw output from the `azdata sql instance list` command
* @param result The raw JSON result array
*/
export function parseSqlInstanceListResult(result: any[]): SqlInstanceListResult[] {
return result.map(r => {
return {
clusterEndpoint: r['Cluster Endpoint'],
externalEndpoint: r['External Endpoint'],
name: r['Name'],
status: r['Status'],
vCores: r['VCores']
};
});
}

View File

@@ -3,22 +3,49 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from './typings/azdata-ext';
import * as vscode from 'vscode';
import { findAzdata } from './azdata';
import { findAzdata, IAzdataTool } from './azdata';
import { parsePostgresServerListResult, parseSqlInstanceListResult } from './common/azdataUtils';
export async function activate(): Promise<void> {
let localAzdata: IAzdataTool | undefined = undefined;
export async function activate(): Promise<azdata.IExtension> {
const outputChannel = vscode.window.createOutputChannel('azdata');
await checkForAzdata(outputChannel);
localAzdata = await checkForAzdata(outputChannel);
return {
postgres: {
server: {
list: async () => {
if (!localAzdata) {
throw new Error('No azdata');
}
return localAzdata.executeCommand(['postgres', 'server', 'list'], parsePostgresServerListResult);
}
}
},
sql: {
instance: {
list: async () => {
if (!localAzdata) {
throw new Error('No azdata');
}
return localAzdata.executeCommand(['sql', 'instance', 'list'], parseSqlInstanceListResult);
}
}
}
};
}
async function checkForAzdata(outputChannel: vscode.OutputChannel): Promise<void> {
async function checkForAzdata(outputChannel: vscode.OutputChannel): Promise<IAzdataTool | undefined> {
try {
await findAzdata(outputChannel);
return await findAzdata(outputChannel);
} catch (err) {
// Don't block on this since we want the extension to finish activating without needing user input.
// Calls will be made to handle azdata not being installed
promptToInstallAzdata(outputChannel).catch(e => console.log(`Unexpected error prompting to install azdata ${e}`));
}
return undefined;
}
async function promptToInstallAzdata(_outputChannel: vscode.OutputChannel): Promise<void> {

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Covers defining what the azdata 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 const enum extension {
name = 'Microsoft.azdata'
}
export interface SqlInstanceListResult {
clusterEndpoint: string,
externalEndpoint: string,
name: string,
status: string,
vCores: string
}
export interface PostgresServerListResult {
id: string,
clusterIP: string,
externalIP: string,
mustRestart: boolean,
name: string,
status: string
}
export interface AzdataOutput<R> {
logs: string[],
result: R[],
stderr: string[],
stdout: string[]
}
export interface IExtension {
postgres: {
server: {
list(): Promise<AzdataOutput<PostgresServerListResult>>
}
},
sql: {
instance: {
list(): Promise<AzdataOutput<SqlInstanceListResult>>
}
}
}