diff --git a/extensions/azdata/src/azdata.ts b/extensions/azdata/src/azdata.ts index eb4d45b938..12194c1de4 100644 --- a/extensions/azdata/src/azdata.ts +++ b/extensions/azdata/src/azdata.ts @@ -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(args: string[], parseResult: (result: any) => R[]): Promise> +} + +class AzdataTool implements IAzdataTool { + constructor(public path: string, public version: string, private _outputChannel: vscode.OutputChannel) { } + + public async executeCommand(args: string[], parseResult: (result: any) => R[]): Promise> { + const output = JSON.parse((await executeCommand(`"${this.path}"`, args.concat(['--output', 'json']), this._outputChannel)).stdout); + return { + logs: output.log, + stdout: output.stdout, + stderr: 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 { +export async function findAzdata(outputChannel: vscode.OutputChannel): Promise { 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 { +async function findAzdataWin32(outputChannel: vscode.OutputChannel): Promise { const promise = searchForCmd('azdata.cmd'); return findSpecificAzdata(await promise, outputChannel); } @@ -122,12 +141,9 @@ async function findAzdataWin32(outputChannel: vscode.OutputChannel): Promise { +async function findSpecificAzdata(path: string, outputChannel: vscode.OutputChannel): Promise { const versionOutput = await executeCommand(path, ['--version'], outputChannel); - return { - path: path, - version: parseVersion(versionOutput.stdout) - }; + return new AzdataTool(path, parseVersion(versionOutput.stdout), outputChannel); } /** diff --git a/extensions/azdata/src/common/azdataUtils.ts b/extensions/azdata/src/common/azdataUtils.ts new file mode 100644 index 0000000000..a6323a9206 --- /dev/null +++ b/extensions/azdata/src/common/azdataUtils.ts @@ -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'] + }; + }); +} diff --git a/extensions/azdata/src/extension.ts b/extensions/azdata/src/extension.ts index dddae17513..f992b977a5 100644 --- a/extensions/azdata/src/extension.ts +++ b/extensions/azdata/src/extension.ts @@ -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 { +let localAzdata: IAzdataTool | undefined = undefined; + +export async function activate(): Promise { 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 { +async function checkForAzdata(outputChannel: vscode.OutputChannel): Promise { 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 { diff --git a/extensions/azdata/src/typings/azdata-ext.d.ts b/extensions/azdata/src/typings/azdata-ext.d.ts new file mode 100644 index 0000000000..0ddaa81dac --- /dev/null +++ b/extensions/azdata/src/typings/azdata-ext.d.ts @@ -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 { + logs: string[], + result: R[], + stderr: string[], + stdout: string[] +} + +export interface IExtension { + postgres: { + server: { + list(): Promise> + } + }, + sql: { + instance: { + list(): Promise> + } + } +}