mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Refactor out extensions-modules and rewrite mssql extension (#909)
* commting .d.ts changes * added serverinfo to .d.ts * maybe its working? * works * updated contrib * remove unnecessary code * fix compile errors * init * conitnue * on the way to working maybe? * close * EVERYTHING WORKS * moved src out of client folder * formatting * reenable logging * working on build file * fixed install service gulp command * fix the command to properly return promises * clean up code * add telemetry * formatting * added logging/telemetry/statusview * formatting * address comments * resolute vscode-language versions
This commit is contained in:
19
extensions/mssql/src/config.json
Normal file
19
extensions/mssql/src/config.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.4.0-alpha.10",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.0.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.0.zip",
|
||||
"OSX": "osx-x64-netcoreapp2.0.tar.gz",
|
||||
"CentOS_7": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"Debian_8": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"Fedora_23": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"OpenSUSE_13_2": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"RHEL_7": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"SLES_12_2": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"Ubuntu_14": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"Ubuntu_16": "rhel-x64-netcoreapp2.0.tar.gz"
|
||||
},
|
||||
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
|
||||
"executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"]
|
||||
}
|
||||
11
extensions/mssql/src/constants.ts
Normal file
11
extensions/mssql/src/constants.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export const serviceName = 'SqlToolsService';
|
||||
export const providerId = 'MSSQL';
|
||||
export const serviceCrashMessage = 'SQL Tools Service component exited unexpectedly. Please restart SQL Operations Studio.';
|
||||
export const serviceCrashButton = 'View Known Issues';
|
||||
export const serviceCrashLink = 'https://github.com/Microsoft/vscode-mssql/wiki/SqlToolsService-Known-Issues';
|
||||
52
extensions/mssql/src/contextProvider.ts
Normal file
52
extensions/mssql/src/contextProvider.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import * as vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export enum BuiltInCommands {
|
||||
SetContext = 'setContext',
|
||||
}
|
||||
|
||||
export enum ContextKeys {
|
||||
ISCLOUD = 'mssql:iscloud'
|
||||
}
|
||||
|
||||
const isCloudEditions = [
|
||||
5,
|
||||
6
|
||||
];
|
||||
|
||||
export function setCommandContext(key: ContextKeys | string, value: any) {
|
||||
return vscode.commands.executeCommand(BuiltInCommands.SetContext, key, value);
|
||||
}
|
||||
|
||||
export default class ContextProvider {
|
||||
private _disposables = new Array<vscode.Disposable>();
|
||||
|
||||
constructor() {
|
||||
this._disposables.push(sqlops.workspace.onDidOpenDashboard(this.onDashboardOpen, this));
|
||||
this._disposables.push(sqlops.workspace.onDidChangeToDashboard(this.onDashboardOpen, this));
|
||||
}
|
||||
|
||||
public onDashboardOpen(e: sqlops.DashboardDocument): void {
|
||||
let iscloud: boolean;
|
||||
if (e.profile.providerName.toLowerCase() === 'mssql' && e.serverInfo.engineEditionId) {
|
||||
if (isCloudEditions.some(i => i === e.serverInfo.engineEditionId)) {
|
||||
iscloud = true;
|
||||
} else {
|
||||
iscloud = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (iscloud === true || iscloud === false) {
|
||||
setCommandContext(ContextKeys.ISCLOUD, iscloud);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables = this._disposables.map(i => i.dispose());
|
||||
}
|
||||
}
|
||||
80
extensions/mssql/src/contracts.ts
Normal file
80
extensions/mssql/src/contracts.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { NotificationType, RequestType } from 'vscode-languageclient';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { ITelemetryEventProperties, ITelemetryEventMeasures } from './telemetry';
|
||||
|
||||
// ------------------------------- < Telemetry Sent Event > ------------------------------------
|
||||
|
||||
/**
|
||||
* Event sent when the language service send a telemetry event
|
||||
*/
|
||||
export namespace TelemetryNotification {
|
||||
export const type = new NotificationType<TelemetryParams, void>('telemetry/sqlevent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event parameters
|
||||
*/
|
||||
export class TelemetryParams {
|
||||
public params: {
|
||||
eventName: string;
|
||||
properties: ITelemetryEventProperties;
|
||||
measures: ITelemetryEventMeasures;
|
||||
};
|
||||
}
|
||||
|
||||
// ------------------------------- </ Telemetry Sent Event > ----------------------------------
|
||||
|
||||
|
||||
// Job Management types
|
||||
export interface AgentJobsParams {
|
||||
ownerUri: string;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface AgentJobsResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
jobs: sqlops.AgentJobInfo[];
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryParams {
|
||||
ownerUri: string;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
jobs: sqlops.AgentJobHistoryInfo[];
|
||||
}
|
||||
|
||||
export interface AgentJobActionParams {
|
||||
ownerUri: string;
|
||||
jobName: string;
|
||||
action: string;
|
||||
}
|
||||
|
||||
export interface AgentJobActionResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export namespace AgentJobsRequest {
|
||||
export const type = new RequestType<AgentJobsParams, AgentJobsResult, void, void>('agent/jobs');
|
||||
}
|
||||
|
||||
export namespace AgentJobHistoryRequest {
|
||||
export const type = new RequestType<AgentJobHistoryParams, AgentJobHistoryResult, void, void>('agent/jobhistory');
|
||||
}
|
||||
|
||||
|
||||
export namespace AgentJobActionRequest {
|
||||
export const type = new RequestType<AgentJobActionParams, AgentJobActionResult, void, void>('agent/jobaction');
|
||||
}
|
||||
8
extensions/mssql/src/credentialstore/constants.ts
Normal file
8
extensions/mssql/src/credentialstore/constants.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export const serviceName = 'SerilizationProvider';
|
||||
export const providerId = 'serilizationProvider';
|
||||
34
extensions/mssql/src/credentialstore/contracts.ts
Normal file
34
extensions/mssql/src/credentialstore/contracts.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { RequestType } from 'vscode-languageclient';
|
||||
import { Credential } from 'sqlops';
|
||||
|
||||
// --------------------------------- < Read Credential Request > -------------------------------------------------
|
||||
|
||||
// Read Credential request message callback declaration
|
||||
export namespace ReadCredentialRequest {
|
||||
export const type = new RequestType<Credential, Credential, void, void>('credential/read');
|
||||
}
|
||||
|
||||
// --------------------------------- </ Read Credential Request > -------------------------------------------------
|
||||
|
||||
// --------------------------------- < Save Credential Request > -------------------------------------------------
|
||||
|
||||
// Save Credential request message callback declaration
|
||||
export namespace SaveCredentialRequest {
|
||||
export const type = new RequestType<Credential, boolean, void, void>('credential/save');
|
||||
}
|
||||
// --------------------------------- </ Save Credential Request > -------------------------------------------------
|
||||
|
||||
|
||||
// --------------------------------- < Delete Credential Request > -------------------------------------------------
|
||||
|
||||
// Delete Credential request message callback declaration
|
||||
export namespace DeleteCredentialRequest {
|
||||
export const type = new RequestType<Credential, boolean, void, void>('credential/delete');
|
||||
}
|
||||
// --------------------------------- </ Delete Credential Request > -------------------------------------------------
|
||||
110
extensions/mssql/src/credentialstore/credentialstore.ts
Normal file
110
extensions/mssql/src/credentialstore/credentialstore.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { SqlOpsDataClient, ClientOptions, SqlOpsFeature } from 'dataprotocol-client';
|
||||
import * as path from 'path';
|
||||
import { IConfig, ServerProvider } from 'service-downloader';
|
||||
import { ServerOptions, RPCMessageType, ClientCapabilities, ServerCapabilities, TransportKind } from 'vscode-languageclient';
|
||||
import { Disposable } from 'vscode';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import * as Contracts from './contracts';
|
||||
import * as Constants from './constants';
|
||||
import * as Utils from '../utils';
|
||||
|
||||
class CredentialsFeature extends SqlOpsFeature<any> {
|
||||
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
Contracts.DeleteCredentialRequest.type,
|
||||
Contracts.SaveCredentialRequest.type,
|
||||
Contracts.ReadCredentialRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, CredentialsFeature.messagesTypes);
|
||||
}
|
||||
|
||||
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
Utils.ensure(Utils.ensure(capabilities, 'credentials')!, 'credentials')!.dynamicRegistration = true;
|
||||
}
|
||||
|
||||
initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: any): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let readCredential = (credentialId: string): Thenable<sqlops.Credential> => {
|
||||
return client.sendRequest(Contracts.ReadCredentialRequest.type, { credentialId });
|
||||
};
|
||||
|
||||
let saveCredential = (credentialId: string, password: string): Thenable<boolean> => {
|
||||
return client.sendRequest(Contracts.SaveCredentialRequest.type, { credentialId, password });
|
||||
};
|
||||
|
||||
let deleteCredential = (credentialId: string): Thenable<boolean> => {
|
||||
return client.sendRequest(Contracts.DeleteCredentialRequest.type, { credentialId });
|
||||
};
|
||||
|
||||
return sqlops.credentials.registerProvider({
|
||||
deleteCredential,
|
||||
readCredential,
|
||||
saveCredential,
|
||||
handle: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a credential storage for Windows, Mac (darwin), or Linux.
|
||||
*
|
||||
* Allows a single credential to be stored per service (that is, one username per service);
|
||||
*/
|
||||
export class CredentialStore {
|
||||
private _client: SqlOpsDataClient;
|
||||
private _config: IConfig;
|
||||
|
||||
constructor(baseConfig: IConfig) {
|
||||
if (baseConfig) {
|
||||
this._config = JSON.parse(JSON.stringify(baseConfig));
|
||||
this._config.executableFiles = ['MicrosoftSqlToolsCredentials.exe', 'MicrosoftSqlToolsCredentials'];
|
||||
}
|
||||
}
|
||||
|
||||
public start() {
|
||||
let serverdownloader = new ServerProvider(this._config);
|
||||
let clientOptions: ClientOptions = {
|
||||
providerId: Constants.providerId,
|
||||
features: [CredentialsFeature]
|
||||
};
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
let serverOptions = this.generateServerOptions(e);
|
||||
this._client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
this._client.start();
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._client) {
|
||||
this._client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private generateServerOptions(executablePath: string): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(Utils.getDefaultLogLocation(), 'mssql');
|
||||
launchArgs.push(logFileLocation);
|
||||
|
||||
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||
}
|
||||
}
|
||||
99
extensions/mssql/src/features.ts
Normal file
99
extensions/mssql/src/features.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
|
||||
import { ClientCapabilities, StaticFeature, RPCMessageType, ServerCapabilities } from 'vscode-languageclient';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
import { Disposable } from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { Telemetry } from './telemetry';
|
||||
import * as Utils from './utils';
|
||||
import { TelemetryNotification, AgentJobsRequest, AgentJobActionRequest, AgentJobHistoryRequest, AgentJobsParams, AgentJobHistoryParams, AgentJobActionParams } from './contracts';
|
||||
|
||||
export class TelemetryFeature implements StaticFeature {
|
||||
|
||||
constructor(private _client: SqlOpsDataClient) { }
|
||||
|
||||
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
Utils.ensure(capabilities, 'telemetry')!.telemetry = true;
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this._client.onNotification(TelemetryNotification.type, e => {
|
||||
Telemetry.sendTelemetryEvent(e.params.eventName, e.params.properties, e.params.measures);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
AgentJobsRequest.type,
|
||||
AgentJobHistoryRequest.type,
|
||||
AgentJobActionRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, AgentServicesFeature.messagesTypes);
|
||||
}
|
||||
|
||||
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
// this isn't explicitly necessary
|
||||
// ensure(ensure(capabilities, 'connection')!, 'agentServices')!.dynamicRegistration = true;
|
||||
}
|
||||
|
||||
public initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let getJobs = (ownerUri: string): Thenable<sqlops.AgentJobsResult> => {
|
||||
let params: AgentJobsParams = { ownerUri: ownerUri, jobId: null };
|
||||
return client.sendRequest(AgentJobsRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobsRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let getJobHistory = (connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> => {
|
||||
let params: AgentJobHistoryParams = { ownerUri: connectionUri, jobId: jobID };
|
||||
|
||||
return client.sendRequest(AgentJobHistoryRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobHistoryRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let jobAction = (connectionUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult> => {
|
||||
let params: AgentJobActionParams = { ownerUri: connectionUri, jobName: jobName, action: action };
|
||||
return client.sendRequest(AgentJobActionRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobActionRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return sqlops.dataprotocol.registerAgentServicesProvider({
|
||||
providerId: client.providerId,
|
||||
getJobs,
|
||||
getJobHistory,
|
||||
jobAction
|
||||
});
|
||||
}
|
||||
}
|
||||
136
extensions/mssql/src/main.ts
Normal file
136
extensions/mssql/src/main.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||
import { IConfig, ServerProvider, Events } from 'service-downloader';
|
||||
import { ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||
|
||||
import * as Constants from './constants';
|
||||
import ContextProvider from './contextProvider';
|
||||
import { CredentialStore } from './credentialstore/credentialstore';
|
||||
import { AzureResourceProvider } from './resourceProvider/resourceProvider';
|
||||
import * as Utils from './utils';
|
||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import { TelemetryFeature } from './features';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
||||
const statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
// lets make sure we support this platform first
|
||||
let supported = await Utils.verifyPlatform();
|
||||
|
||||
if (!supported) {
|
||||
vscode.window.showErrorMessage('Unsupported platform');
|
||||
return;
|
||||
}
|
||||
|
||||
let config: IConfig = JSON.parse(JSON.stringify(baseConfig));
|
||||
config.installDirectory = path.join(__dirname, config.installDirectory);
|
||||
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
|
||||
config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
|
||||
|
||||
const credentialsStore = new CredentialStore(config);
|
||||
const resourceProvider = new AzureResourceProvider(config);
|
||||
let languageClient: SqlOpsDataClient;
|
||||
|
||||
const serverdownloader = new ServerProvider(config);
|
||||
|
||||
serverdownloader.eventEmitter.onAny(generateHandleServerProviderEvent());
|
||||
|
||||
let clientOptions: ClientOptions = {
|
||||
providerId: Constants.providerId,
|
||||
errorHandler: new LanguageClientErrorHandler(),
|
||||
features: [
|
||||
// we only want to add new features
|
||||
...SqlOpsDataClient.defaultFeatures,
|
||||
TelemetryFeature
|
||||
]
|
||||
};
|
||||
|
||||
const installationStart = Date.now();
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
const installationComplete = Date.now();
|
||||
let serverOptions = generateServerOptions(e);
|
||||
languageClient = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
const processStart = Date.now();
|
||||
languageClient.onReady().then(() => {
|
||||
const processEnd = Date.now();
|
||||
statusView.text = 'Service Started';
|
||||
setTimeout(() => {
|
||||
statusView.hide();
|
||||
}, 1500);
|
||||
Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
|
||||
installationTime: String(installationComplete - installationStart),
|
||||
processStartupTime: String(processEnd - processStart),
|
||||
totalTime: String(processEnd - installationStart),
|
||||
beginningTimestamp: String(installationStart)
|
||||
});
|
||||
});
|
||||
statusView.show();
|
||||
statusView.text = 'Starting service';
|
||||
languageClient.start();
|
||||
credentialsStore.start();
|
||||
resourceProvider.start();
|
||||
}, e => {
|
||||
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
|
||||
vscode.window.showErrorMessage('Failed to start Sql tools service');
|
||||
});
|
||||
|
||||
let contextProvider = new ContextProvider();
|
||||
context.subscriptions.push(contextProvider);
|
||||
context.subscriptions.push(credentialsStore);
|
||||
context.subscriptions.push(resourceProvider);
|
||||
context.subscriptions.push({ dispose: () => languageClient.stop() });
|
||||
}
|
||||
|
||||
function generateServerOptions(executablePath: string): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(Utils.getDefaultLogLocation(), 'mssql');
|
||||
launchArgs.push(logFileLocation);
|
||||
|
||||
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||
}
|
||||
|
||||
function generateHandleServerProviderEvent() {
|
||||
let dots = 0;
|
||||
return (e: string, ...args: any[]) => {
|
||||
outputChannel.show();
|
||||
statusView.show();
|
||||
switch (e) {
|
||||
case Events.INSTALL_START:
|
||||
outputChannel.appendLine(`Installing ${Constants.serviceName} service to ${args[0]}`);
|
||||
statusView.text = 'Installing Service';
|
||||
break;
|
||||
case Events.INSTALL_END:
|
||||
outputChannel.appendLine('Installed');
|
||||
break;
|
||||
case Events.DOWNLOAD_START:
|
||||
outputChannel.appendLine(`Downloading ${args[0]}`);
|
||||
outputChannel.append(`(${Math.ceil(args[1] / 1024)} KB)`);
|
||||
statusView.text = 'Downloading Service';
|
||||
break;
|
||||
case Events.DOWNLOAD_PROGRESS:
|
||||
let newDots = Math.ceil(args[0] / 5);
|
||||
if (newDots > dots) {
|
||||
outputChannel.append('.'.repeat(newDots - dots));
|
||||
dots = newDots;
|
||||
}
|
||||
break;
|
||||
case Events.DOWNLOAD_END:
|
||||
outputChannel.appendLine('Done!');
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate(): void {
|
||||
}
|
||||
8
extensions/mssql/src/resourceprovider/constants.ts
Normal file
8
extensions/mssql/src/resourceprovider/constants.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export const serviceName = 'AzureResourceProvider';
|
||||
export const providerId = 'azureresourceProvider';
|
||||
42
extensions/mssql/src/resourceprovider/contracts.ts
Normal file
42
extensions/mssql/src/resourceprovider/contracts.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { RequestType } from 'vscode-languageclient';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
// ------------------------------- < Resource Events > ------------------------------------
|
||||
export namespace CreateFirewallRuleRequest {
|
||||
export const type = new RequestType<CreateFirewallRuleParams, CreateFirewallRuleResponse, void, void>('resource/createFirewallRule');
|
||||
}
|
||||
|
||||
export namespace HandleFirewallRuleRequest {
|
||||
export const type = new RequestType<HandleFirewallRuleParams, HandleFirewallRuleResponse, void, void>('resource/handleFirewallRule');
|
||||
}
|
||||
|
||||
// Firewall rule interfaces
|
||||
export interface CreateFirewallRuleParams {
|
||||
account: sqlops.Account;
|
||||
serverName: string;
|
||||
startIpAddress: string;
|
||||
endIpAddress: string;
|
||||
securityTokenMappings: {};
|
||||
}
|
||||
|
||||
export interface CreateFirewallRuleResponse {
|
||||
result: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export interface HandleFirewallRuleParams {
|
||||
errorCode: number;
|
||||
errorMessage: string;
|
||||
connectionTypeId: string;
|
||||
}
|
||||
|
||||
export interface HandleFirewallRuleResponse {
|
||||
result: boolean;
|
||||
ipAddress: string;
|
||||
}
|
||||
115
extensions/mssql/src/resourceprovider/resourceprovider.ts
Normal file
115
extensions/mssql/src/resourceprovider/resourceprovider.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import { IConfig, ServerProvider } from 'service-downloader';
|
||||
import { SqlOpsDataClient, SqlOpsFeature, ClientOptions } from 'dataprotocol-client';
|
||||
import { ServerCapabilities, ClientCapabilities, RPCMessageType, ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { Disposable } from 'vscode';
|
||||
|
||||
import { CreateFirewallRuleRequest, HandleFirewallRuleRequest, CreateFirewallRuleParams, HandleFirewallRuleParams } from './contracts';
|
||||
import * as Constants from './constants';
|
||||
import * as Utils from '../utils';
|
||||
|
||||
class FireWallFeature extends SqlOpsFeature<any> {
|
||||
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
CreateFirewallRuleRequest.type,
|
||||
HandleFirewallRuleRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, FireWallFeature.messagesTypes);
|
||||
}
|
||||
|
||||
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
Utils.ensure(Utils.ensure(capabilities, 'firewall')!, 'firwall')!.dynamicRegistration = true;
|
||||
}
|
||||
|
||||
initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: any): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let createFirewallRule = (account: sqlops.Account, firewallruleInfo: sqlops.FirewallRuleInfo): Thenable<sqlops.CreateFirewallRuleResponse> => {
|
||||
return client.sendRequest(CreateFirewallRuleRequest.type, asCreateFirewallRuleParams(account, firewallruleInfo));
|
||||
};
|
||||
|
||||
let handleFirewallRule = (errorCode: number, errorMessage: string, connectionTypeId: string): Thenable<sqlops.HandleFirewallRuleResponse> => {
|
||||
let params: HandleFirewallRuleParams = { errorCode: errorCode, errorMessage: errorMessage, connectionTypeId: connectionTypeId };
|
||||
return client.sendRequest(HandleFirewallRuleRequest.type, params);
|
||||
};
|
||||
|
||||
return sqlops.resources.registerResourceProvider({
|
||||
displayName: 'Azure SQL Resource Provider', // TODO Localize
|
||||
id: 'Microsoft.Azure.SQL.ResourceProvider',
|
||||
settings: {
|
||||
|
||||
}
|
||||
}, {
|
||||
handleFirewallRule,
|
||||
createFirewallRule
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function asCreateFirewallRuleParams(account: sqlops.Account, params: sqlops.FirewallRuleInfo): CreateFirewallRuleParams {
|
||||
return {
|
||||
account: account,
|
||||
serverName: params.serverName,
|
||||
startIpAddress: params.startIpAddress,
|
||||
endIpAddress: params.endIpAddress,
|
||||
securityTokenMappings: params.securityTokenMappings
|
||||
};
|
||||
}
|
||||
|
||||
export class AzureResourceProvider {
|
||||
private _client: SqlOpsDataClient;
|
||||
private _config: IConfig;
|
||||
|
||||
constructor(baseConfig: IConfig) {
|
||||
if (baseConfig) {
|
||||
this._config = JSON.parse(JSON.stringify(baseConfig));
|
||||
this._config.executableFiles = ['SqlToolsResourceProviderService.exe', 'SqlToolsResourceProviderService'];
|
||||
}
|
||||
}
|
||||
|
||||
public start() {
|
||||
let serverdownloader = new ServerProvider(this._config);
|
||||
let clientOptions: ClientOptions = {
|
||||
providerId: Constants.providerId,
|
||||
features: [FireWallFeature]
|
||||
};
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
let serverOptions = this.generateServerOptions(e);
|
||||
this._client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
this._client.start();
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this._client) {
|
||||
this._client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private generateServerOptions(executablePath: string): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(Utils.getDefaultLogLocation(), 'mssql');
|
||||
launchArgs.push(logFileLocation);
|
||||
|
||||
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||
}
|
||||
}
|
||||
211
extensions/mssql/src/telemetry.ts
Normal file
211
extensions/mssql/src/telemetry.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as vscode from 'vscode';
|
||||
import * as opener from 'opener';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import { PlatformInformation } from 'service-downloader/out/platform';
|
||||
import { ErrorAction, ErrorHandler, Message, CloseAction } from 'vscode-languageclient';
|
||||
|
||||
import * as Utils from './utils';
|
||||
import * as Constants from './constants';
|
||||
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
export interface ITelemetryEventProperties {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface ITelemetryEventMeasures {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters error paths to only include source files. Exported to support testing
|
||||
*/
|
||||
export function FilterErrorPath(line: string): string {
|
||||
if (line) {
|
||||
let values: string[] = line.split('/out/');
|
||||
if (values.length <= 1) {
|
||||
// Didn't match expected format
|
||||
return line;
|
||||
} else {
|
||||
return values[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Telemetry {
|
||||
private static reporter: TelemetryReporter;
|
||||
private static userId: string;
|
||||
private static platformInformation: PlatformInformation;
|
||||
private static disabled: boolean;
|
||||
|
||||
// Get the unique ID for the current user of the extension
|
||||
public static getUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
// Generate the user id if it has not been created already
|
||||
if (typeof this.userId === 'undefined') {
|
||||
let id = Utils.generateUserId();
|
||||
id.then(newId => {
|
||||
this.userId = newId;
|
||||
resolve(this.userId);
|
||||
});
|
||||
} else {
|
||||
resolve(this.userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getPlatformInformation(): Promise<PlatformInformation> {
|
||||
if (this.platformInformation) {
|
||||
return Promise.resolve(this.platformInformation);
|
||||
} else {
|
||||
return new Promise<PlatformInformation>(resolve => {
|
||||
PlatformInformation.getCurrent().then(info => {
|
||||
this.platformInformation = info;
|
||||
resolve(this.platformInformation);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable telemetry reporting
|
||||
*/
|
||||
public static disable(): void {
|
||||
this.disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the telemetry reporter for use.
|
||||
*/
|
||||
public static initialize(): void {
|
||||
if (typeof this.reporter === 'undefined') {
|
||||
// Check if the user has opted out of telemetry
|
||||
if (!vscode.workspace.getConfiguration('telemetry').get<boolean>('enableTelemetry', true)) {
|
||||
this.disable();
|
||||
return;
|
||||
}
|
||||
|
||||
let packageInfo = Utils.getPackageInfo(packageJson);
|
||||
this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a telemetry event for an exception
|
||||
*/
|
||||
public static sendTelemetryEventForException(
|
||||
err: any, methodName: string, extensionConfigName: string): void {
|
||||
try {
|
||||
let stackArray: string[];
|
||||
let firstLine: string = '';
|
||||
if (err !== undefined && err.stack !== undefined) {
|
||||
stackArray = err.stack.split('\n');
|
||||
if (stackArray !== undefined && stackArray.length >= 2) {
|
||||
firstLine = stackArray[1]; // The fist line is the error message and we don't want to send that telemetry event
|
||||
firstLine = FilterErrorPath(firstLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
|
||||
this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
|
||||
// Utils.logDebug('Unhandled Exception occurred. error: ' + err + ' method: ' + methodName, extensionConfigName);
|
||||
} catch (telemetryErr) {
|
||||
// If sending telemetry event fails ignore it so it won't break the extension
|
||||
// Utils.logDebug('Failed to send telemetry event. error: ' + telemetryErr, extensionConfigName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a telemetry event using application insights
|
||||
*/
|
||||
public static sendTelemetryEvent(
|
||||
eventName: string,
|
||||
properties?: ITelemetryEventProperties,
|
||||
measures?: ITelemetryEventMeasures): void {
|
||||
|
||||
if (typeof this.disabled === 'undefined') {
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
if (this.disabled || typeof (this.reporter) === 'undefined') {
|
||||
// Don't do anything if telemetry is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties || typeof properties === 'undefined') {
|
||||
properties = {};
|
||||
}
|
||||
|
||||
// Augment the properties structure with additional common properties before sending
|
||||
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
|
||||
properties['userId'] = this.userId;
|
||||
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
|
||||
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';
|
||||
|
||||
this.reporter.sendTelemetryEvent(eventName, properties, measures);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Language Service client errors
|
||||
* @class LanguageClientErrorHandler
|
||||
*/
|
||||
export class LanguageClientErrorHandler implements ErrorHandler {
|
||||
|
||||
/**
|
||||
* Show an error message prompt with a link to known issues wiki page
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
showOnErrorPrompt(): void {
|
||||
Telemetry.sendTelemetryEvent(Constants.serviceName + 'Crash');
|
||||
vscode.window.showErrorMessage(
|
||||
Constants.serviceCrashMessage,
|
||||
Constants.serviceCrashButton).then(action => {
|
||||
if (action && action === Constants.serviceCrashButton) {
|
||||
opener(Constants.serviceCrashLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client error
|
||||
*
|
||||
* @param {Error} error
|
||||
* @param {Message} message
|
||||
* @param {number} count
|
||||
* @returns {ErrorAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
error(error: Error, message: Message, count: number): ErrorAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return ErrorAction.Shutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client closed
|
||||
*
|
||||
* @returns {CloseAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
closed(): CloseAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return CloseAction.DoNotRestart;
|
||||
}
|
||||
}
|
||||
|
||||
Telemetry.initialize();
|
||||
7
extensions/mssql/src/typings/refs.d.ts
vendored
Normal file
7
extensions/mssql/src/typings/refs.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
103
extensions/mssql/src/utils.ts
Normal file
103
extensions/mssql/src/utils.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
|
||||
// The function is a duplicate of \src\paths.js. IT would be better to import path.js but it doesn't
|
||||
// work for now because the extension is running in different process.
|
||||
export function getAppDataPath() {
|
||||
let platform = process.platform;
|
||||
switch (platform) {
|
||||
case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming');
|
||||
case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support');
|
||||
case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
||||
default: throw new Error('Platform not supported');
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultLogLocation() {
|
||||
return path.join(getAppDataPath(), 'sqlops');
|
||||
}
|
||||
|
||||
export function ensure(target: object, key: string): any {
|
||||
if (target[key] === void 0) {
|
||||
target[key] = {} as any;
|
||||
}
|
||||
return target[key];
|
||||
}
|
||||
|
||||
export interface IPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
aiKey: string;
|
||||
}
|
||||
|
||||
export function getPackageInfo(packageJson: any): IPackageInfo {
|
||||
if (packageJson) {
|
||||
return {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
aiKey: packageJson.aiKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function generateUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
try {
|
||||
let interfaces = os.networkInterfaces();
|
||||
let mac;
|
||||
for (let key of Object.keys(interfaces)) {
|
||||
let item = interfaces[key][0];
|
||||
if (!item.internal) {
|
||||
mac = item.mac;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mac) {
|
||||
resolve(crypto.createHash('sha256').update(mac + os.homedir(), 'utf8').digest('hex'));
|
||||
} else {
|
||||
resolve(generateGuid());
|
||||
}
|
||||
} catch (err) {
|
||||
resolve(generateGuid()); // fallback
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function generateGuid(): string {
|
||||
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||
let oct: string = '';
|
||||
let tmp: number;
|
||||
/* tslint:disable:no-bitwise */
|
||||
for (let a: number = 0; a < 4; a++) {
|
||||
tmp = (4294967296 * Math.random()) | 0;
|
||||
oct += hexValues[tmp & 0xF] +
|
||||
hexValues[tmp >> 4 & 0xF] +
|
||||
hexValues[tmp >> 8 & 0xF] +
|
||||
hexValues[tmp >> 12 & 0xF] +
|
||||
hexValues[tmp >> 16 & 0xF] +
|
||||
hexValues[tmp >> 20 & 0xF] +
|
||||
hexValues[tmp >> 24 & 0xF] +
|
||||
hexValues[tmp >> 28 & 0xF];
|
||||
}
|
||||
|
||||
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||
/* tslint:enable:no-bitwise */
|
||||
}
|
||||
|
||||
export function verifyPlatform(): Thenable<boolean> {
|
||||
if (os.platform() === 'darwin' && parseFloat(os.release()) < 16.0) {
|
||||
return Promise.resolve(false);
|
||||
} else {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user