Extensions Cleanup (#359)

* clean up extensions

* updated copyrights

* formatting
This commit is contained in:
Anthony Dresser
2017-12-20 21:35:52 -08:00
committed by GitHub
parent b1b3a92717
commit 8afebd2e10
72 changed files with 2352 additions and 5359 deletions

View File

@@ -1 +1 @@
sqltoolsservice
sqltoolsservice

View File

@@ -0,0 +1,4 @@
client/src/**
client/tsconfig.json
npm-shrinkwrap.json
test/**

View File

@@ -1,447 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 adal from 'adal-node';
import * as data from 'data';
import * as request from 'request';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import * as url from 'url';
import {
Arguments,
AzureAccount,
AzureAccountProviderMetadata,
AzureAccountSecurityTokenCollection,
Tenant
} from './interfaces';
import TokenCache from './tokenCache';
const localize = nls.loadMessageBundle();
export class AzureAccountProvider implements data.AccountProvider {
// CONSTANTS ///////////////////////////////////////////////////////////
private static WorkSchoolAccountType: string = 'work_school';
private static MicrosoftAccountType: string = 'microsoft';
private static AadCommonTenant: string = 'common';
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _args: Arguments;
private _autoOAuthCancelled: boolean;
private _commonAuthorityUrl: string;
private _inProgressAutoOAuth: InProgressAutoOAuth;
private _isInitialized: boolean;
constructor(private _metadata: AzureAccountProviderMetadata, private _tokenCache: TokenCache) {
this._args = {
host: this._metadata.settings.host,
clientId: this._metadata.settings.clientId
};
this._autoOAuthCancelled = false;
this._inProgressAutoOAuth = null;
this._isInitialized = false;
this._commonAuthorityUrl = url.resolve(this._metadata.settings.host, AzureAccountProvider.AadCommonTenant);
}
// PUBLIC METHODS //////////////////////////////////////////////////////
public autoOAuthCancelled(): Thenable<void> {
return this.doIfInitialized(() => this.cancelAutoOAuth());
}
/**
* Clears all tokens that belong to the given account from the token cache
* @param {"data".AccountKey} accountKey Key identifying the account to delete tokens for
* @returns {Thenable<void>} Promise to clear requested tokens from the token cache
*/
public clear(accountKey: data.AccountKey): Thenable<void> {
return this.doIfInitialized(() => this.clearAccountTokens(accountKey));
}
/**
* Clears the entire token cache. Invoked by command palette action.
* @returns {Thenable<void>} Promise to clear the token cache
*/
public clearTokenCache(): Thenable<void> {
return this._tokenCache.clear();
}
public getSecurityToken(account: AzureAccount): Thenable<AzureAccountSecurityTokenCollection> {
return this.doIfInitialized(() => this.getAccessTokens(account));
}
public initialize(restoredAccounts: data.Account[]): Thenable<data.Account[]> {
let self = this;
let rehydrationTasks: Thenable<data.Account>[] = [];
for (let account of restoredAccounts) {
// Purge any invalid accounts
if (!account) {
continue;
}
// Refresh the contextual logo based on whether the account is a MS account
account.displayInfo.accountType = account.properties.isMsAccount
? AzureAccountProvider.MicrosoftAccountType
: AzureAccountProvider.WorkSchoolAccountType;
// Attempt to get fresh tokens. If this fails then the account is stale.
// NOTE: Based on ADAL implementation, getting tokens should use the refresh token if necessary
let task = this.getAccessTokens(account)
.then(
() => {
return account;
},
() => {
account.isStale = true;
return account;
}
);
rehydrationTasks.push(task);
}
// Collect the rehydration tasks and mark the provider as initialized
return Promise.all(rehydrationTasks)
.then(accounts => {
self._isInitialized = true;
return accounts;
});
}
public prompt(): Thenable<AzureAccount> {
return this.doIfInitialized(() => this.signIn(true));
}
public refresh(account: AzureAccount): Thenable<AzureAccount> {
return this.doIfInitialized(() => this.signIn(false));
}
// PRIVATE METHODS /////////////////////////////////////////////////////
private cancelAutoOAuth(): Thenable<void> {
let self = this;
if (!this._inProgressAutoOAuth) {
console.warn('Attempted to cancel auto OAuth when auto OAuth is not in progress!');
return Promise.resolve();
}
// Indicate oauth was cancelled by the user
let inProgress = self._inProgressAutoOAuth;
self._autoOAuthCancelled = true;
self._inProgressAutoOAuth = null;
// Use the auth context that was originally used to open the polling request, and cancel the polling
let context = inProgress.context;
context.cancelRequestToGetTokenWithDeviceCode(inProgress.userCodeInfo, err => {
// Callback is only called in failure scenarios.
if (err) {
console.warn(`Error while cancelling auto OAuth: ${err}`);
}
});
return Promise.resolve();
}
private clearAccountTokens(accountKey: data.AccountKey): Thenable<void> {
// Put together a query to look up any tokens associated with the account key
let query = <adal.TokenResponse>{ userId: accountKey.accountId };
// 1) Look up the tokens associated with the query
// 2) Remove them
return this._tokenCache.findThenable(query)
.then(results => this._tokenCache.removeThenable(results));
}
private doIfInitialized<T>(op: () => Thenable<T>): Thenable<T> {
return this._isInitialized
? op()
: Promise.reject(localize('accountProviderNotInitialized', 'Account provider not initialized, cannot perform action'));
}
private getAccessTokens(account: AzureAccount): Thenable<AzureAccountSecurityTokenCollection> {
let self = this;
let accessTokenPromises: Thenable<void>[] = [];
let tokenCollection: AzureAccountSecurityTokenCollection = {};
for (let tenant of account.properties.tenants) {
let promise = new Promise<void>((resolve, reject) => {
let authorityUrl = url.resolve(self._metadata.settings.host, tenant.id);
let context = new adal.AuthenticationContext(authorityUrl, null, self._tokenCache);
context.acquireToken(
self._metadata.settings.armResource.id,
tenant.userId,
self._metadata.settings.clientId,
(error: Error, response: adal.TokenResponse | adal.ErrorResponse) => {
// Handle errors first
if (error) {
// TODO: We'll assume for now that the account is stale, though that might not be accurate
account.isStale = true;
data.accounts.accountUpdated(account);
reject(error);
return;
}
// We know that the response was not an error
let tokenResponse = <adal.TokenResponse>response;
// Generate a token object and add it to the collection
tokenCollection[tenant.id] = {
expiresOn: tokenResponse.expiresOn,
resource: tokenResponse.resource,
token: tokenResponse.accessToken,
tokenType: tokenResponse.tokenType
};
resolve();
}
);
});
accessTokenPromises.push(promise);
}
// Wait until all the tokens have been acquired then return the collection
return Promise.all(accessTokenPromises)
.then(() => tokenCollection);
}
private getDeviceLoginUserCode(): Thenable<InProgressAutoOAuth> {
let self = this;
// Create authentication context and acquire user code
return new Promise<InProgressAutoOAuth>((resolve, reject) => {
let context = new adal.AuthenticationContext(self._commonAuthorityUrl, null, self._tokenCache);
context.acquireUserCode(self._metadata.settings.signInResourceId, self._metadata.settings.clientId, vscode.env.language,
(err, response) => {
if (err) {
reject(err);
} else {
let result: InProgressAutoOAuth = {
context: context,
userCodeInfo: response
};
resolve(result);
}
}
);
});
}
private getDeviceLoginToken(oAuth: InProgressAutoOAuth, isAddAccount: boolean): Thenable<adal.TokenResponse> {
let self = this;
// 1) Open the auto OAuth dialog
// 2) Begin the acquiring token polling
// 3) When that completes via callback, close the auto oauth
let title = isAddAccount ?
localize('addAccount', 'Add {0} account', self._metadata.displayName) :
localize('refreshAccount', 'Refresh {0} account', self._metadata.displayName);
return data.accounts.beginAutoOAuthDeviceCode(self._metadata.id, title, oAuth.userCodeInfo.message, oAuth.userCodeInfo.userCode, oAuth.userCodeInfo.verificationUrl)
.then(() => {
return new Promise<adal.TokenResponse>((resolve, reject) => {
let context = oAuth.context;
context.acquireTokenWithDeviceCode(self._metadata.settings.signInResourceId, self._metadata.settings.clientId, oAuth.userCodeInfo,
(err, response) => {
if (err) {
if (self._autoOAuthCancelled) {
// Auto OAuth was cancelled by the user, indicate this with the error we return
reject(<data.UserCancelledSignInError>{ userCancelledSignIn: true });
} else {
// Auto OAuth failed for some other reason
data.accounts.endAutoOAuthDeviceCode();
reject(err);
}
} else {
data.accounts.endAutoOAuthDeviceCode();
resolve(<adal.TokenResponse>response);
}
}
);
});
});
}
private getTenants(userId: string, homeTenant: string): Thenable<Tenant[]> {
let self = this;
// 1) Get a token we can use for looking up the tenant IDs
// 2) Send a request to the ARM endpoint (the root management API) to get the list of tenant IDs
// 3) For all the tenants
// b) Get a token we can use for the AAD Graph API
// a) Get the display name of the tenant
// c) create a tenant object
// 4) Sort to make sure the "home tenant" is the first tenant on the list
return this.getToken(userId, AzureAccountProvider.AadCommonTenant, this._metadata.settings.armResource.id)
.then((armToken: adal.TokenResponse) => {
let tenantUri = url.resolve(self._metadata.settings.armResource.endpoint, 'tenants?api-version=2015-01-01');
return self.makeWebRequest(armToken, tenantUri);
})
.then((tenantResponse: any[]) => {
let promises: Thenable<Tenant>[] = tenantResponse.map(value => {
return self.getToken(userId, value.tenantId, self._metadata.settings.graphResource.id)
.then((graphToken: adal.TokenResponse) => {
let tenantDetailsUri = url.resolve(self._metadata.settings.graphResource.endpoint, value.tenantId + '/');
tenantDetailsUri = url.resolve(tenantDetailsUri, 'tenantDetails?api-version=2013-04-05');
return self.makeWebRequest(graphToken, tenantDetailsUri);
})
.then((tenantDetails: any) => {
return <Tenant>{
id: value.tenantId,
userId: userId,
displayName: tenantDetails.length && tenantDetails[0].displayName
? tenantDetails[0].displayName
: localize('azureWorkAccountDisplayName', 'Work or school account')
};
});
});
return Promise.all(promises);
})
.then((tenants: Tenant[]) => {
let homeTenantIndex = tenants.findIndex(tenant => tenant.id === homeTenant);
if (homeTenantIndex >= 0) {
let homeTenant = tenants.splice(homeTenantIndex, 1);
tenants.unshift(homeTenant[0]);
}
return tenants;
});
}
/**
* Retrieves a token for the given user ID for the specific tenant ID. If the token can, it
* will be retrieved from the cache as per the ADAL API. AFAIK, the ADAL API will also utilize
* the refresh token if there aren't any unexpired tokens to use.
* @param {string} userId ID of the user to get a token for
* @param {string} tenantId Tenant to get the token for
* @param {string} resourceId ID of the resource the token will be good for
* @returns {Thenable<TokenResponse>} Promise to return a token. Rejected if retrieving the token fails.
*/
private getToken(userId: string, tenantId: string, resourceId: string): Thenable<adal.TokenResponse> {
let self = this;
return new Promise<adal.TokenResponse>((resolve, reject) => {
let authorityUrl = url.resolve(self._metadata.settings.host, tenantId);
let context = new adal.AuthenticationContext(authorityUrl, null, self._tokenCache);
context.acquireToken(resourceId, userId, self._metadata.settings.clientId,
(error: Error, response: adal.TokenResponse | adal.ErrorResponse) => {
if (error) {
reject(error);
} else {
resolve(<adal.TokenResponse>response);
}
}
);
});
}
/**
* Performs a web request using the provided bearer token
* @param {TokenResponse} accessToken Bearer token for accessing the provided URI
* @param {string} uri URI to access
* @returns {Thenable<any>} Promise to return the deserialized body of the request. Rejected if error occurred.
*/
private makeWebRequest(accessToken: adal.TokenResponse, uri: string): Thenable<any> {
return new Promise<any>((resolve, reject) => {
// Setup parameters for the request
// NOTE: setting json true means the returned object will be deserialized
let params = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken.accessToken}`
},
json: true
};
// Setup the callback to resolve/reject this promise
let callback = (error, response, body: { error: any; value: any; }) => {
if (error || body.error) {
reject(error || JSON.stringify(body.error));
} else {
resolve(body.value);
}
};
// Make the request
request.get(uri, params, callback);
});
}
private signIn(isAddAccount: boolean): Thenable<AzureAccount> {
let self = this;
// 1) Get the user code for this login
// 2) Get an access token from the device code
// 3) Get the list of tenants
// 4) Generate the AzureAccount object and return it
let tokenResponse: adal.TokenResponse = null;
return this.getDeviceLoginUserCode()
.then((result: InProgressAutoOAuth) => {
self._autoOAuthCancelled = false;
self._inProgressAutoOAuth = result;
return self.getDeviceLoginToken(self._inProgressAutoOAuth, isAddAccount);
})
.then((response: adal.TokenResponse) => {
tokenResponse = response;
self._autoOAuthCancelled = false;
self._inProgressAutoOAuth = null;
return self.getTenants(tokenResponse.userId, tokenResponse.userId);
})
.then((tenants: Tenant[]) => {
// Figure out where we're getting the identity from
let identityProvider = tokenResponse.identityProvider;
if (identityProvider) {
identityProvider = identityProvider.toLowerCase();
}
// Determine if this is a microsoft account
let msa = identityProvider && (
identityProvider.indexOf('live.com') !== -1 ||
identityProvider.indexOf('live-int.com') !== -1 ||
identityProvider.indexOf('f8cdef31-a31e-4b4a-93e4-5f571e91255a') !== -1 ||
identityProvider.indexOf('ea8a4392-515e-481f-879e-6571ff2a8a36') !== -1);
// Calculate the display name for the user
let displayName = (tokenResponse.givenName && tokenResponse.familyName)
? `${tokenResponse.givenName} ${tokenResponse.familyName}`
: tokenResponse.userId;
// Calculate the home tenant display name to use for the contextual display name
let contextualDisplayName = msa
? localize('microsoftAccountDisplayName', 'Microsoft Account')
: tenants[0].displayName;
// Calculate the account type
let accountType = msa
? AzureAccountProvider.MicrosoftAccountType
: AzureAccountProvider.WorkSchoolAccountType;
return <AzureAccount>{
key: {
providerId: self._metadata.id,
accountId: tokenResponse.userId
},
name: tokenResponse.userId,
displayInfo: {
accountType: accountType,
contextualDisplayName: contextualDisplayName,
displayName: displayName
},
properties: {
isMsAccount: msa,
tenants: tenants
},
isStale: false
};
});
}
}
interface InProgressAutoOAuth {
context: adal.AuthenticationContext;
userCodeInfo: adal.UserCodeInfo;
}

View File

@@ -1,163 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 data from 'data';
import * as events from 'events';
import * as path from 'path';
import * as vscode from 'vscode';
import CredentialServiceTokenCache from './tokenCache';
import providerSettings from './providerSettings';
import { AzureAccountProvider } from './azureAccountProvider';
import { AzureAccountProviderMetadata, ProviderSettings } from './interfaces';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class AzureAccountProviderService implements vscode.Disposable {
// CONSTANTS ///////////////////////////////////////////////////////////////
private static CommandClearTokenCache = 'extension.clearTokenCache';
private static ConfigurationSection = 'accounts.azure';
private static CredentialNamespace = 'azureAccountProviderCredentials';
// MEMBER VARIABLES ////////////////////////////////////////////////////////
private _accountDisposals: { [accountProviderId: string]: vscode.Disposable };
private _accountProviders: { [accountProviderId: string]: AzureAccountProvider };
private _credentialProvider: data.CredentialProvider;
private _configChangePromiseChain: Thenable<void>;
private _currentConfig: vscode.WorkspaceConfiguration;
private _event: events.EventEmitter;
constructor(private _context: vscode.ExtensionContext, private _userStoragePath: string) {
this._accountDisposals = {};
this._accountProviders = {};
this._configChangePromiseChain = Promise.resolve();
this._currentConfig = null;
this._event = new events.EventEmitter();
}
// PUBLIC METHODS //////////////////////////////////////////////////////
public activate(): Thenable<boolean> {
let self = this;
// Register commands
this._context.subscriptions.push(vscode.commands.registerCommand(
AzureAccountProviderService.CommandClearTokenCache,
() => { self._event.emit(AzureAccountProviderService.CommandClearTokenCache); }
));
this._event.on(AzureAccountProviderService.CommandClearTokenCache, () => { self.onClearTokenCache(); });
// 1) Get a credential provider
// 2a) Store the credential provider for use later
// 2b) Register the configuration change handler
// 2c) Perform an initial config change handling
return data.credentials.getProvider(AzureAccountProviderService.CredentialNamespace)
.then(credProvider => {
self._credentialProvider = credProvider;
self._context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(self.onDidChangeConfiguration, self));
self.onDidChangeConfiguration();
return true;
});
}
public dispose() { }
// PRIVATE HELPERS /////////////////////////////////////////////////////
private onClearTokenCache(): Thenable<void> {
let self = this;
let promises: Thenable<void>[] = providerSettings.map(provider => {
return self._accountProviders[provider.metadata.id].clearTokenCache();
});
return Promise.all(promises)
.then(
() => {
let message = localize('clearTokenCacheSuccess', 'Token cache successfully cleared');
vscode.window.showInformationMessage(`mssql: ${message}`);
},
err => {
let message = localize('clearTokenCacheFailure', 'Failed to clear token cache');
vscode.window.showErrorMessage(`mssql: ${message}: ${err}`);
});
}
private onDidChangeConfiguration(): void {
let self = this;
// Add a new change processing onto the existing promise change
this._configChangePromiseChain = this._configChangePromiseChain.then(() => {
// Grab the stored config and the latest config
let newConfig = vscode.workspace.getConfiguration(AzureAccountProviderService.ConfigurationSection);
let oldConfig = self._currentConfig;
self._currentConfig = newConfig;
// Determine what providers need to be changed
let providerChanges: Thenable<void>[] = [];
for(let provider of providerSettings) {
// If the old config doesn't exist, then assume everything was disabled
// There will always be a new config value
let oldConfigValue = oldConfig
? oldConfig.get<boolean>(provider.configKey)
: false;
let newConfigValue = newConfig.get<boolean>(provider.configKey);
// Case 1: Provider config has not changed - do nothing
if (oldConfigValue === newConfigValue) {
continue;
}
// Case 2: Provider was enabled and is now disabled - unregister provider
if (oldConfigValue && !newConfigValue) {
providerChanges.push(self.unregisterAccountProvider(provider));
}
// Case 3: Provider was disabled and is now enabled - register provider
if (!oldConfigValue && newConfigValue) {
providerChanges.push(self.registerAccountProvider(provider));
}
}
// Process all the changes before continuing
return Promise.all(providerChanges);
}).then(null, () => { return Promise.resolve(); });
}
private registerAccountProvider(provider: ProviderSettings): Thenable<void> {
let self = this;
return new Promise((resolve, reject) => {
try {
let tokenCacheKey = `azureTokenCache-${provider.metadata.id}`;
let tokenCachePath = path.join(this._userStoragePath, tokenCacheKey);
let tokenCache = new CredentialServiceTokenCache(self._credentialProvider, tokenCacheKey, tokenCachePath);
let accountProvider = new AzureAccountProvider(<AzureAccountProviderMetadata>provider.metadata, tokenCache);
self._accountProviders[provider.metadata.id] = accountProvider;
self._accountDisposals[provider.metadata.id] = data.accounts.registerAccountProvider(provider.metadata, accountProvider);
resolve();
} catch(e) {
console.error(`Failed to register account provider: ${e}`);
reject(e);
}
});
}
private unregisterAccountProvider(provider: ProviderSettings): Thenable<void> {
let self = this;
return new Promise((resolve, reject) => {
try {
self._accountDisposals[provider.metadata.id].dispose();
delete self._accountProviders[provider.metadata.id];
delete self._accountDisposals[provider.metadata.id];
resolve();
} catch(e) {
console.error(`Failed to unregister account provider: ${e}`);
reject(e);
}
});
}
}

View File

@@ -1,182 +0,0 @@
'use strict';
import * as data from 'data';
/**
* Represents a tenant (an Azure Active Directory instance) to which a user has access
*/
export interface Tenant {
/**
* Globally unique identifier of the tenant
*/
id: string;
/**
* Display name of the tenant
*/
displayName: string;
/**
* Identifier of the user in the tenant
*/
userId: string;
}
/**
* Represents a resource exposed by an Azure Active Directory
*/
export interface Resource {
/**
* Identifier of the resource
*/
id: string;
/**
* Endpoint url used to access the resource
*/
endpoint: string;
}
/**
* Represents the arguments that identify an instantiation of the AAD account provider
*/
export interface Arguments {
/**
* Host of the authority
*/
host: string;
/**
* Identifier of the client application
*/
clientId: string;
}
/**
* Represents settings for an AAD account provider
*/
export interface Settings {
/**
* Host of the authority
*/
host?: string;
/**
* Identifier of the client application
*/
clientId?: string;
/**
* Identifier of the resource to request when signing in
*/
signInResourceId?: string;
/**
* Information that describes the AAD graph resource
*/
graphResource?: Resource;
/**
* Information that describes the Azure resource management resource
*/
armResource?: Resource;
/**
* A list of tenant IDs to authenticate against. If defined, then these IDs will be used
* instead of querying the tenants endpoint of the armResource
*/
adTenants?: string[];
// AuthorizationCodeGrantFlowSettings //////////////////////////////////
/**
* An optional site ID that brands the interactive aspect of sign in
*/
siteId?: string;
/**
* Redirect URI that is used to signify the end of the interactive aspect of sign it
*/
redirectUri?: string;
}
/**
* Mapping of configuration key with the metadata to instantiate the account provider
*/
export interface ProviderSettings {
/**
* Key for configuration regarding whether the account provider is enabled
*/
configKey: string;
/**
* Metadata for the provider
*/
metadata: AzureAccountProviderMetadata;
}
/**
* Extension of account provider metadata to override settings type for Azure account providers
*/
export interface AzureAccountProviderMetadata extends data.AccountProviderMetadata {
/**
* Azure specific account provider settings.
*/
settings: Settings;
}
/**
* Properties specific to an Azure account
*/
export interface AzureAccountProperties {
/**
* Whether or not the account is a Microsoft account
*/
isMsAccount: boolean;
/**
* A list of tenants (aka directories) that the account belongs to
*/
tenants: Tenant[];
}
/**
* Override of the Account type to enforce properties that are AzureAccountProperties
*/
export interface AzureAccount extends data.Account {
/**
* AzureAccountProperties specifically used for Azure accounts
*/
properties: AzureAccountProperties;
}
/**
* Token returned from a request for an access token
*/
export interface AzureAccountSecurityToken {
/**
* Access token, itself
*/
token: string;
/**
* Date that the token expires on
*/
expiresOn: Date | string;
/**
* Name of the resource the token is good for (ie, management.core.windows.net)
*/
resource: string;
/**
* Type of the token (pretty much always 'Bearer')
*/
tokenType: string;
}
/**
* Azure account security token maps a tenant ID to the information returned from a request to get
* an access token. The list of tenants correspond to the tenants in the account properties.
*/
export type AzureAccountSecurityTokenCollection = {[tenantId: string]: AzureAccountSecurityToken};

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><defs><style>.cls-1{fill:#fff;}</style></defs><title>microsoft_logo-white</title><rect class="cls-1" x="0.06" y="0.06" width="5.65" height="5.65"/><rect class="cls-1" x="6.29" y="0.06" width="5.65" height="5.65"/><rect class="cls-1" x="0.06" y="6.3" width="5.65" height="5.65"/><rect class="cls-1" x="6.29" y="6.3" width="5.65" height="5.65"/></svg>

Before

Width:  |  Height:  |  Size: 442 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}</style></defs><title>microsoft_account_Blk</title><rect class="cls-1" width="7.6" height="7.6"/><rect class="cls-1" x="8.4" width="7.6" height="7.6"/><rect class="cls-1" y="8.4" width="7.6" height="7.6"/><rect class="cls-1" x="8.4" y="8.4" width="7.6" height="7.6"/></svg>

Before

Width:  |  Height:  |  Size: 400 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>other_account_inverse_16x16</title><path class="cls-1" d="M7.92.29A7.71,7.71,0,1,0,15.64,8,7.73,7.73,0,0,0,7.92.29ZM4.12,13.54a4,4,0,0,1,.13-.93,3.69,3.69,0,0,1,1-1.66A3.56,3.56,0,0,1,6,10.37a4,4,0,0,1,.9-.38,4.17,4.17,0,0,1,1-.13,3.79,3.79,0,0,1,1.48.29,3.61,3.61,0,0,1,2,2,3.74,3.74,0,0,1,.29,1.47,6.62,6.62,0,0,1-3.7,1.12A6.71,6.71,0,0,1,4.12,13.54Zm2-5a2.76,2.76,0,0,1-.54-.79,2.43,2.43,0,0,1-.19-1,2.4,2.4,0,0,1,.19-1A2.82,2.82,0,0,1,6.1,5a2.81,2.81,0,0,1,.8-.54,2.4,2.4,0,0,1,1-.19,2.43,2.43,0,0,1,1,.19A2.76,2.76,0,0,1,9.63,5a2.46,2.46,0,0,1,.54.8,2.4,2.4,0,0,1,.2,1,2.44,2.44,0,0,1-.2,1A2.59,2.59,0,0,1,8.84,9a2.44,2.44,0,0,1-1,.2,2.4,2.4,0,0,1-1-.2A2.46,2.46,0,0,1,6.1,8.49Zm6.12,4.66a4.39,4.39,0,0,0-.18-.89,4.22,4.22,0,0,0-.57-1.19A4.24,4.24,0,0,0,9.36,9.49,3.41,3.41,0,0,0,10,9a3.36,3.36,0,0,0,.84-1.42A3.32,3.32,0,0,0,11,6.74a3.09,3.09,0,0,0-.24-1.22,3.26,3.26,0,0,0-.67-1,3,3,0,0,0-1-.67,3,3,0,0,0-1.22-.25,2.93,2.93,0,0,0-1.22.25A3.07,3.07,0,0,0,5,5.51a2.93,2.93,0,0,0-.25,1.22,3,3,0,0,0,.12.84,3,3,0,0,0,.32.76A3.3,3.3,0,0,0,5.7,9a3.06,3.06,0,0,0,.68.5,4.41,4.41,0,0,0-1.19.65,4.1,4.1,0,0,0-.91,1,4.18,4.18,0,0,0-.58,1.18,4.41,4.41,0,0,0-.18.8A6.66,6.66,0,0,1,1.2,8a6.72,6.72,0,1,1,11,5.15Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>other_account_16x16</title><path d="M8,.28A7.71,7.71,0,1,0,15.72,8,7.73,7.73,0,0,0,8,.28ZM4.2,13.53a4,4,0,0,1,.13-.93,3.69,3.69,0,0,1,1-1.66A3.56,3.56,0,0,1,6,10.36a4,4,0,0,1,.9-.38,4.17,4.17,0,0,1,1-.13,3.79,3.79,0,0,1,1.48.29,3.61,3.61,0,0,1,2,2,3.74,3.74,0,0,1,.29,1.47A6.62,6.62,0,0,1,8,14.71,6.71,6.71,0,0,1,4.2,13.53Zm2-5a2.76,2.76,0,0,1-.54-.79,2.43,2.43,0,0,1-.19-1,2.4,2.4,0,0,1,.19-1A2.82,2.82,0,0,1,6.18,5,2.81,2.81,0,0,1,7,4.42a2.4,2.4,0,0,1,1-.19,2.43,2.43,0,0,1,1,.19A2.76,2.76,0,0,1,9.71,5a2.46,2.46,0,0,1,.54.8,2.4,2.4,0,0,1,.2,1,2.44,2.44,0,0,1-.2,1A2.59,2.59,0,0,1,8.92,9a2.44,2.44,0,0,1-1,.2A2.4,2.4,0,0,1,7,9,2.46,2.46,0,0,1,6.18,8.49Zm6.12,4.66a4.39,4.39,0,0,0-.18-.89,4.22,4.22,0,0,0-.57-1.19A4.24,4.24,0,0,0,9.44,9.48a3.41,3.41,0,0,0,.68-.5A3.36,3.36,0,0,0,11,7.56a3.32,3.32,0,0,0,.11-.83,3.09,3.09,0,0,0-.24-1.22,3.26,3.26,0,0,0-.67-1,3,3,0,0,0-1-.67A3,3,0,0,0,7.95,3.6a2.93,2.93,0,0,0-1.22.25A3.07,3.07,0,0,0,5.07,5.51a2.93,2.93,0,0,0-.25,1.22,3,3,0,0,0,.12.84,3,3,0,0,0,.32.76A3.3,3.3,0,0,0,5.78,9a3.06,3.06,0,0,0,.68.5,4.41,4.41,0,0,0-1.19.65,4.1,4.1,0,0,0-.91,1,4.18,4.18,0,0,0-.58,1.18,4.41,4.41,0,0,0-.18.8A6.66,6.66,0,0,1,1.28,8a6.72,6.72,0,1,1,11,5.15Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import { ProviderSettings } from './interfaces';
const localize = nls.loadMessageBundle();
const publicAzureSettings: ProviderSettings = {
configKey: 'enablePublicCloud',
metadata: {
displayName: localize('publicCloudDisplayName', 'Azure'),
id: 'azurePublicCloud',
settings: {
host: 'https://login.microsoftonline.com/',
clientId: 'a69788c6-1d43-44ed-9ca3-b83e194da255',
signInResourceId: 'https://management.core.windows.net/',
graphResource: {
id: 'https://graph.windows.net/',
endpoint: 'https://graph.windows.net'
},
armResource: {
id: 'https://management.core.windows.net/',
endpoint: 'https://management.azure.com'
},
redirectUri: 'http://localhost/redirect'
}
}
};
const usGovAzureSettings: ProviderSettings = {
configKey: 'enableUsGovCloud',
metadata: {
displayName: localize('usGovCloudDisplayName', 'Azure (US Government)'),
id: 'usGovAzureCloud',
settings: {
host: 'https://login.microsoftonline.com/',
clientId: 'TBD',
signInResourceId: 'https://management.core.usgovcloudapi.net/',
graphResource: {
id: 'https://graph.usgovcloudapi.net/',
endpoint: 'https://graph.usgovcloudapi.net'
},
armResource: {
id: 'https://management.core.usgovcloudapi.net/',
endpoint: 'https://management.usgovcloudapi.net'
},
redirectUri: 'http://localhost/redirect'
}
}
};
const chinaAzureSettings: ProviderSettings = {
configKey: 'enableChinaCloud',
metadata: {
displayName: localize('chinaCloudDisplayName', 'Azure (China)'),
id: 'chinaAzureCloud',
settings: {
host: 'https://login.chinacloudapi.cn/',
clientId: 'TBD',
signInResourceId: 'https://management.core.chinacloudapi.cn/',
graphResource: {
id: 'https://graph.chinacloudapi.cn/',
endpoint: 'https://graph.chinacloudapi.cn'
},
armResource: {
id: 'https://management.core.chinacloudapi.cn/',
endpoint: 'https://managemement.chinacloudapi.net'
},
redirectUri: 'http://localhost/redirect'
}
}
};
const germanyAzureSettings: ProviderSettings = {
configKey: 'enableGermanyCloud',
metadata: {
displayName: localize('germanyCloud', 'Azure (Germany)'),
id: 'germanyAzureCloud',
settings: {
host: 'https://login.microsoftazure.de/',
clientId: 'TBD',
signInResourceId: 'https://management.core.cloudapi.de/',
graphResource: {
id: 'https://graph.cloudapi.de/',
endpoint: 'https://graph.cloudapi.de'
},
armResource: {
id: 'https://management.core.cloudapi.de/',
endpoint: 'https://management.microsoftazure.de'
},
redirectUri: 'http://localhost/redirect'
}
}
};
// TODO: Enable China, Germany, and US Gov clouds: (#3031)
export default <ProviderSettings[]>[publicAzureSettings, /*chinaAzureSettings, germanyAzureSettings, usGovAzureSettings*/];

View File

@@ -1,291 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 adal from 'adal-node';
import * as data from 'data';
import * as crypto from 'crypto';
import * as fs from 'fs';
export default class TokenCache implements adal.TokenCache {
private static CipherAlgorithm = 'aes256';
private static CipherAlgorithmIvLength = 16;
private static CipherKeyLength = 32;
private static FsOptions = { encoding: 'ascii' };
private _activeOperation: Thenable<any>;
constructor(
private _credentialProvider: data.CredentialProvider,
private _credentialServiceKey: string,
private _cacheSerializationPath: string
) {
}
// PUBLIC METHODS //////////////////////////////////////////////////////
public add(entries: adal.TokenResponse[], callback: (error: Error, result: boolean) => void): void {
let self = this;
this.doOperation(() => {
return self.readCache()
.then(cache => self.addToCache(cache, entries))
.then(updatedCache => self.writeCache(updatedCache))
.then(
() => callback(null, false),
(err) => callback(err, true)
);
});
}
public clear(): Thenable<void> {
let self = this;
// 1) Delete encrypted serialization file
// If we got an 'ENOENT' response, the file doesn't exist, which is fine
// 3) Delete the encryption key
return new Promise<void>((resolve, reject) => {
fs.unlink(self._cacheSerializationPath, err => {
if (err && err.code !== 'ENOENT') {
reject(err);
} else {
resolve();
}
});
})
.then(() => { return self._credentialProvider.deleteCredential(self._credentialServiceKey); })
.then(() => {});
}
public find(query: any, callback: (error: Error, results: any[]) => void): void {
let self = this;
this.doOperation(() => {
return self.readCache()
.then(cache => {
return cache.filter(
entry => TokenCache.findByPartial(entry, query)
);
})
.then(
results => callback(null, results),
(err) => callback(err, null)
);
});
}
/**
* Wrapper to make callback-based find method into a thenable method
* @param query Partial object to use to look up tokens. Ideally should be partial of adal.TokenResponse
* @returns {Thenable<any[]>} Promise to return the matching adal.TokenResponse objects.
* Rejected if an error was sent in the callback
*/
public findThenable(query: any): Thenable<any[]> {
let self = this;
return new Promise<any[]>((resolve, reject) => {
self.find(query, (error: Error, results: any[]) => {
if (error) {
reject(error);
} else {
resolve(results);
}
});
});
}
public remove(entries: adal.TokenResponse[], callback: (error: Error, result: null) => void): void {
let self = this;
this.doOperation(() => {
return this.readCache()
.then(cache => self.removeFromCache(cache, entries))
.then(updatedCache => self.writeCache(updatedCache))
.then(
() => callback(null, null),
(err) => callback(err, null)
);
});
}
/**
* Wrapper to make callback-based remove method into a thenable method
* @param {TokenResponse[]} entries Array of entries to remove from the token cache
* @returns {Thenable<void>} Promise to remove the given tokens from the token cache
* Rejected if an error was sent in the callback
*/
public removeThenable(entries: adal.TokenResponse[]): Thenable<void> {
let self = this;
return new Promise<void>((resolve, reject) => {
self.remove(entries, (error: Error, result: null) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
// PRIVATE METHODS /////////////////////////////////////////////////////
private static findByKeyHelper(entry1: adal.TokenResponse, entry2: adal.TokenResponse): boolean {
return entry1._authority === entry2._authority
&& entry1._clientId === entry2._clientId
&& entry1.userId === entry2.userId
&& entry1.resource === entry2.resource;
}
private static findByPartial(entry: adal.TokenResponse, query: object): boolean {
for (let key in query) {
if (entry[key] === undefined || entry[key] !== query[key]) {
return false;
}
}
return true;
}
private doOperation<T>(op: () => Thenable<T>): void {
// Initialize the active operation to an empty promise if necessary
let activeOperation = this._activeOperation || Promise.resolve<any>(null);
// Chain the operation to perform to the end of the existing promise
activeOperation = activeOperation.then(op);
// Add a catch at the end to make sure we can continue after any errors
activeOperation = activeOperation.then(null, err => {
console.error(`Failed to perform token cache operation: ${err}`);
});
// Point the current active operation to this one
this._activeOperation = activeOperation;
}
private addToCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] {
// First remove entries from the db that are being updated
cache = this.removeFromCache(cache, entries);
// Then add the new entries to the cache
entries.forEach((entry: adal.TokenResponse) => {
cache.push(entry);
});
return cache;
}
private getOrCreateEncryptionParams(): Thenable<EncryptionParams> {
let self = this;
return this._credentialProvider.readCredential(this._credentialServiceKey)
.then(credential => {
if (credential.password) {
// We already have encryption params, deserialize them
let splitValues = credential.password.split('|');
if (splitValues.length === 2 && splitValues[0] && splitValues[1]) {
try {
return <EncryptionParams>{
key: new Buffer(splitValues[0], 'hex'),
initializationVector: new Buffer(splitValues[1], 'hex')
};
} catch(e) {
// Swallow the error and fall through to generate new params
console.warn('Failed to deserialize encryption params, new ones will be generated.');
}
}
}
// We haven't stored encryption values, so generate them
let encryptKey = crypto.randomBytes(TokenCache.CipherKeyLength);
let initializationVector = crypto.randomBytes(TokenCache.CipherAlgorithmIvLength);
// Serialize the values
let serializedValues = `${encryptKey.toString('hex')}|${initializationVector.toString('hex')}`;
return self._credentialProvider.saveCredential(self._credentialServiceKey, serializedValues)
.then(() => {
return <EncryptionParams> {
key: encryptKey,
initializationVector: initializationVector
};
});
});
}
private readCache(): Thenable<adal.TokenResponse[]> {
let self = this;
// NOTE: File system operations are performed synchronously to avoid annoying nested callbacks
// 1) Get the encryption key
// 2) Read the encrypted token cache file
// 3) Decrypt the file contents
// 4) Deserialize and return
return this.getOrCreateEncryptionParams()
.then(encryptionParams => {
try {
let cacheCipher = fs.readFileSync(self._cacheSerializationPath, TokenCache.FsOptions);
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheJson = decipher.update(cacheCipher, 'hex', 'binary');
cacheJson += decipher.final('binary');
// Deserialize the JSON into the array of tokens
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
for (let objIndex in cacheObj) {
// Rehydrate Date objects since they will always serialize as a string
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
}
return cacheObj;
} catch(e) {
throw e;
}
})
.then(null, err => {
// If reading the token cache fails, we'll just assume the tokens are garbage
console.warn(`Failed to read token cache: ${err}`);
return [];
});
}
private removeFromCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] {
entries.forEach((entry: adal.TokenResponse) => {
// Check to see if the entry exists
let match = cache.findIndex(entry2 => TokenCache.findByKeyHelper(entry, entry2));
if (match >= 0) {
// Entry exists, remove it from cache
cache.splice(match, 1);
}
});
return cache;
}
private writeCache(cache: adal.TokenResponse[]): Thenable<void> {
let self = this;
// NOTE: File system operations are being done synchronously to avoid annoying callback nesting
// 1) Get (or generate) the encryption key
// 2) Stringify the token cache entries
// 4) Encrypt the JSON
// 3) Write to the file
return this.getOrCreateEncryptionParams()
.then(encryptionParams => {
try {
let cacheJson = JSON.stringify(cache);
let cipher = crypto.createCipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheCipher = cipher.update(cacheJson, 'binary', 'hex');
cacheCipher += cipher.final('hex');
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
} catch (e) {
throw e;
}
});
}
}
interface EncryptionParams {
key: Buffer;
initializationVector: Buffer;
}

View File

@@ -8,183 +8,158 @@ import vscode = require('vscode');
import data = require('data');
import { Constants } from '../models/constants';
import { Serialization } from '../serialize/serialization';
import { AzureResourceProvider } from '../resourceprovider/resourceprovider';
import { AzureResourceProvider } from '../resourceProvider/resourceProvider';
import { CredentialStore } from '../credentialstore/credentialstore';
import {IExtensionConstants, Telemetry, SharedConstants, SqlToolsServiceClient, VscodeWrapper, Utils, PlatformInformation} from 'extensions-modules';
import { IExtensionConstants, Telemetry, Constants as SharedConstants, SqlToolsServiceClient, VscodeWrapper, Utils, PlatformInformation } from 'extensions-modules';
import { LanguageClient } from 'dataprotocol-client';
import * as fs from 'fs';
import * as path from 'path';
import { AzureAccountProviderService } from '../account-provider/azureAccountProviderService';
/**
* The main controller class that initializes the extension
*/
export default class MainController implements vscode.Disposable {
private _context: vscode.ExtensionContext;
private _vscodeWrapper: VscodeWrapper;
private _initialized: boolean = false;
private _serialization: Serialization;
private _credentialStore: CredentialStore;
private static _extensionConstants: IExtensionConstants = new Constants();
private _client: SqlToolsServiceClient;
/**
* The main controller constructor
* @constructor
*/
constructor(context: vscode.ExtensionContext,
vscodeWrapper?: VscodeWrapper) {
this._context = context;
this._vscodeWrapper = vscodeWrapper || new VscodeWrapper(MainController._extensionConstants);
SqlToolsServiceClient.constants = MainController._extensionConstants;
this._client = SqlToolsServiceClient.instance;
this._credentialStore = new CredentialStore(this._client);
this._serialization = new Serialization(this._client);
}
private _context: vscode.ExtensionContext;
private _vscodeWrapper: VscodeWrapper;
private _initialized: boolean = false;
private _serialization: Serialization;
private _credentialStore: CredentialStore;
private static _extensionConstants: IExtensionConstants = new Constants();
private _client: SqlToolsServiceClient;
/**
* The main controller constructor
* @constructor
*/
constructor(context: vscode.ExtensionContext,
vscodeWrapper?: VscodeWrapper) {
this._context = context;
this._vscodeWrapper = vscodeWrapper || new VscodeWrapper(MainController._extensionConstants);
SqlToolsServiceClient.constants = MainController._extensionConstants;
this._client = SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json'));
this._credentialStore = new CredentialStore(this._client);
this._serialization = new Serialization(this._client);
}
/**
* Disposes the controller
*/
dispose(): void {
this.deactivate();
}
/**
* Disposes the controller
*/
dispose(): void {
this.deactivate();
}
/**
* Deactivates the extension
*/
public deactivate(): void {
Utils.logDebug(SharedConstants.extensionDeactivated, MainController._extensionConstants.extensionConfigSectionName);
}
/**
* Deactivates the extension
*/
public deactivate(): void {
Utils.logDebug(SharedConstants.extensionDeactivated, MainController._extensionConstants.extensionConfigSectionName);
}
/**
* Initializes the extension
*/
public activate(): Promise<boolean> {
return this.initialize();
}
/**
* Initializes the extension
*/
public activate(): Promise<boolean> {
return this.initialize();
}
/**
* Returns a flag indicating if the extension is initialized
*/
public isInitialized(): boolean {
return this._initialized;
}
/**
* Returns a flag indicating if the extension is initialized
*/
public isInitialized(): boolean {
return this._initialized;
}
private createClient( executableFiles: string[]): Promise<LanguageClient> {
return PlatformInformation.getCurrent(SqlToolsServiceClient.constants.getRuntimeId, SqlToolsServiceClient.constants.extensionName).then( platformInfo => {
return SqlToolsServiceClient.instance.createClient(this._context, platformInfo.runtimeId, undefined, executableFiles);
});
}
private createClient(executableFiles: string[]): Promise<LanguageClient> {
return PlatformInformation.getCurrent(SqlToolsServiceClient.constants.getRuntimeId, SqlToolsServiceClient.constants.extensionName).then(platformInfo => {
return SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json')).createClient(this._context, platformInfo.runtimeId, undefined, executableFiles);
});
}
private createCredentialClient(): Promise<LanguageClient> {
return this.createClient(['MicrosoftSqlToolsCredentials.exe', 'MicrosoftSqlToolsCredentials']);
}
private createCredentialClient(): Promise<LanguageClient> {
return this.createClient(['MicrosoftSqlToolsCredentials.exe', 'MicrosoftSqlToolsCredentials']);
}
private createSerializationClient(): Promise<LanguageClient> {
return this.createClient(['MicrosoftSqlToolsSerialization.exe', 'MicrosoftSqlToolsSerialization']);
}
private createSerializationClient(): Promise<LanguageClient> {
return this.createClient(['MicrosoftSqlToolsSerialization.exe', 'MicrosoftSqlToolsSerialization']);
}
private createResourceProviderClient(): Promise<LanguageClient> {
return this.createClient(['SqlToolsResourceProviderService.exe', 'SqlToolsResourceProviderService']);
}
private createResourceProviderClient(): Promise<LanguageClient> {
return this.createClient(['SqlToolsResourceProviderService.exe', 'SqlToolsResourceProviderService']);
}
/**
* Initializes the extension
*/
public initialize(): Promise<boolean> {
const self = this;
/**
* Initializes the extension
*/
public initialize(): Promise<boolean> {
// initialize language service client
return new Promise<boolean>( (resolve, reject) => {
// initialize language service client
return new Promise<boolean>((resolve, reject) => {
const self = this;
SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json')).initialize(self._context).then(serverResult => {
let constants = new Constants();
// Initialize telemetry
Telemetry.initialize(self._context, new Constants());
// Create the folder for storing the token caches
let storagePath = path.join(Utils.getDefaultLogLocation(), constants.extensionName);
try {
if (!fs.existsSync(storagePath)) {
fs.mkdirSync(storagePath);
console.log('Initialized Azure account extension storage.');
}
} catch(e) {
console.error(`Initialization of Azure account extension storage failed: ${e}`);
console.error('Azure accounts will not be available');
return;
}
// telemetry for activation
Telemetry.sendTelemetryEvent('ExtensionActivated', {},
{ serviceInstalled: serverResult.installedBeforeInitializing ? 1 : 0 }
);
// Create the provider service and activate
const accountProviderService = new AzureAccountProviderService(this._context, storagePath);
this._context.subscriptions.push(accountProviderService);
accountProviderService.activate();
self.createSerializationClient().then(serializationClient => {
// Serialization
let serializationProvider: data.SerializationProvider = {
handle: 0,
saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Thenable<data.SaveResultRequestResult> {
return self._serialization.saveAs(saveFormat, savePath, results, appendToFile);
}
};
data.serialization.registerProvider(serializationProvider);
}, error => {
Utils.logDebug('Cannot find Serialization executables. error: ' + error, MainController._extensionConstants.extensionConfigSectionName);
});
const self = this;
SqlToolsServiceClient.instance.initialize(self._context).then(serverResult => {
self.createResourceProviderClient().then(rpClient => {
let resourceProvider = new AzureResourceProvider(self._client, rpClient);
data.resources.registerResourceProvider({
displayName: 'Azure SQL Resource Provider', // TODO Localize
id: 'Microsoft.Azure.SQL.ResourceProvider',
settings: {
// Initialize telemetry
Telemetry.initialize(self._context, new Constants());
}
}, resourceProvider);
Utils.logDebug('resourceProvider registered', MainController._extensionConstants.extensionConfigSectionName);
}, error => {
Utils.logDebug('Cannot find ResourceProvider executables. error: ' + error, MainController._extensionConstants.extensionConfigSectionName);
});
// telemetry for activation
Telemetry.sendTelemetryEvent('ExtensionActivated', {},
{ serviceInstalled: serverResult.installedBeforeInitializing ? 1 : 0 }
);
self.createCredentialClient().then(credentialClient => {
self.createSerializationClient().then(serializationClient => {
let serialization = new Serialization(self._client, serializationClient);
// Serialization
let serializationProvider: data.SerializationProvider = {
handle: 0,
saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Thenable<data.SaveResultRequestResult> {
return self._serialization.saveAs(saveFormat, savePath, results, appendToFile);
}
};
data.serialization.registerProvider(serializationProvider);
}, error => {
Utils.logDebug('Cannot find Serialization executables. error: ' + error , MainController._extensionConstants.extensionConfigSectionName);
});
self._credentialStore.languageClient = credentialClient;
let credentialProvider: data.CredentialProvider = {
handle: 0,
saveCredential(credentialId: string, password: string): Thenable<boolean> {
return self._credentialStore.saveCredential(credentialId, password);
},
readCredential(credentialId: string): Thenable<data.Credential> {
return self._credentialStore.readCredential(credentialId);
},
deleteCredential(credentialId: string): Thenable<boolean> {
return self._credentialStore.deleteCredential(credentialId);
}
};
data.credentials.registerProvider(credentialProvider);
Utils.logDebug('credentialProvider registered', MainController._extensionConstants.extensionConfigSectionName);
}, error => {
Utils.logDebug('Cannot find credentials executables. error: ' + error, MainController._extensionConstants.extensionConfigSectionName);
});
self.createResourceProviderClient().then(rpClient => {
let resourceProvider = new AzureResourceProvider(self._client, rpClient);
data.resources.registerResourceProvider({
displayName: 'Azure SQL Resource Provider', // TODO Localize
id: 'Microsoft.Azure.SQL.ResourceProvider',
settings: {
}
}, resourceProvider);
Utils.logDebug('resourceProvider registered', MainController._extensionConstants.extensionConfigSectionName);
}, error => {
Utils.logDebug('Cannot find ResourceProvider executables. error: ' + error , MainController._extensionConstants.extensionConfigSectionName);
});
self.createCredentialClient().then(credentialClient => {
self._credentialStore.languageClient = credentialClient;
let credentialProvider: data.CredentialProvider = {
handle: 0,
saveCredential(credentialId: string, password: string): Thenable<boolean> {
return self._credentialStore.saveCredential(credentialId, password);
},
readCredential(credentialId: string): Thenable<data.Credential> {
return self._credentialStore.readCredential(credentialId);
},
deleteCredential(credentialId: string): Thenable<boolean> {
return self._credentialStore.deleteCredential(credentialId);
}
};
data.credentials.registerProvider(credentialProvider);
Utils.logDebug('credentialProvider registered', MainController._extensionConstants.extensionConfigSectionName);
}, error => {
Utils.logDebug('Cannot find credentials executables. error: ' + error , MainController._extensionConstants.extensionConfigSectionName);
});
Utils.logDebug(SharedConstants.extensionActivated, MainController._extensionConstants.extensionConfigSectionName);
self._initialized = true;
resolve(true);
}).catch(err => {
Telemetry.sendTelemetryEventForException(err, 'initialize', MainController._extensionConstants.extensionConfigSectionName);
reject(err);
});
});
}
Utils.logDebug(SharedConstants.extensionActivated, MainController._extensionConstants.extensionConfigSectionName);
self._initialized = true;
resolve(true);
}).catch(err => {
Telemetry.sendTelemetryEventForException(err, 'initialize', MainController._extensionConstants.extensionConfigSectionName);
reject(err);
});
});
}
}

View File

@@ -1,13 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
* 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 Contracts from '../models/contracts';
import { ICredentialStore } from './icredentialstore';
import { SqlToolsServiceClient, Utils } from 'extensions-modules';
import { LanguageClient } from 'dataprotocol-client';
import * as path from 'path';
/**
* Implements a credential storage for Windows, Mac (darwin), or Linux.
@@ -16,58 +17,58 @@ import { LanguageClient } from 'dataprotocol-client';
*/
export class CredentialStore implements ICredentialStore {
public languageClient: LanguageClient;
public languageClient: LanguageClient;
constructor(private _client?: SqlToolsServiceClient) {
if (!this._client) {
this._client = SqlToolsServiceClient.instance;
}
}
constructor(private _client?: SqlToolsServiceClient) {
if (!this._client) {
this._client = SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json'));
}
}
/**
* Gets a credential saved in the credential store
*
* @param {string} credentialId the ID uniquely identifying this credential
* @returns {Promise<Credential>} Promise that resolved to the credential, or undefined if not found
*/
public readCredential(credentialId: string): Promise<Contracts.Credential> {
Utils.logDebug(this.languageClient, 'MainController._extensionConstants');
let self = this;
let cred: Contracts.Credential = new Contracts.Credential();
cred.credentialId = credentialId;
return new Promise<Contracts.Credential>( (resolve, reject) => {
self._client
.sendRequest(Contracts.ReadCredentialRequest.type, cred, this.languageClient)
.then(returnedCred => {
resolve(<Contracts.Credential>returnedCred);
}, err => reject(err));
});
}
/**
* Gets a credential saved in the credential store
*
* @param {string} credentialId the ID uniquely identifying this credential
* @returns {Promise<Credential>} Promise that resolved to the credential, or undefined if not found
*/
public readCredential(credentialId: string): Promise<Contracts.Credential> {
Utils.logDebug(this.languageClient, 'MainController._extensionConstants');
let self = this;
let cred: Contracts.Credential = new Contracts.Credential();
cred.credentialId = credentialId;
return new Promise<Contracts.Credential>((resolve, reject) => {
self._client
.sendRequest(Contracts.ReadCredentialRequest.type, cred, this.languageClient)
.then(returnedCred => {
resolve(<Contracts.Credential>returnedCred);
}, err => reject(err));
});
}
public saveCredential(credentialId: string, password: any): Promise<boolean> {
let self = this;
let cred: Contracts.Credential = new Contracts.Credential();
cred.credentialId = credentialId;
cred.password = password;
return new Promise<boolean>( (resolve, reject) => {
self._client
.sendRequest(Contracts.SaveCredentialRequest.type, cred, this.languageClient)
.then(status => {
resolve(<boolean>status);
}, err => reject(err));
});
}
public saveCredential(credentialId: string, password: any): Promise<boolean> {
let self = this;
let cred: Contracts.Credential = new Contracts.Credential();
cred.credentialId = credentialId;
cred.password = password;
return new Promise<boolean>((resolve, reject) => {
self._client
.sendRequest(Contracts.SaveCredentialRequest.type, cred, this.languageClient)
.then(status => {
resolve(<boolean>status);
}, err => reject(err));
});
}
public deleteCredential(credentialId: string): Promise<boolean> {
let self = this;
let cred: Contracts.Credential = new Contracts.Credential();
cred.credentialId = credentialId;
return new Promise<boolean>( (resolve, reject) => {
self._client
.sendRequest(Contracts.DeleteCredentialRequest.type, cred, this.languageClient)
.then(status => {
resolve(<boolean>status);
}, err => reject(err));
});
}
public deleteCredential(credentialId: string): Promise<boolean> {
let self = this;
let cred: Contracts.Credential = new Contracts.Credential();
cred.credentialId = credentialId;
return new Promise<boolean>((resolve, reject) => {
self._client
.sendRequest(Contracts.DeleteCredentialRequest.type, cred, this.languageClient)
.then(status => {
resolve(<boolean>status);
}, err => reject(err));
});
}
}

View File

@@ -1,7 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
@@ -17,7 +17,7 @@ import { Credential } from '../models/contracts';
* @interface ICredentialStore
*/
export interface ICredentialStore {
readCredential(credentialId: string): Promise<Credential>;
saveCredential(credentialId: string, password: any): Promise<boolean>;
deleteCredential(credentialId: string): Promise<boolean>;
readCredential(credentialId: string): Promise<Credential>;
saveCredential(credentialId: string, password: any): Promise<boolean>;
deleteCredential(credentialId: string): Promise<boolean>;
}

View File

@@ -7,7 +7,7 @@
import vscode = require('vscode');
import MainController from './controllers/mainController';
let controller: MainController = undefined;
export let controller: MainController;
export function activate(context: vscode.ExtensionContext) {
controller = new MainController(context);
@@ -17,14 +17,7 @@ export function activate(context: vscode.ExtensionContext) {
// this method is called when your extension is deactivated
export function deactivate(): void {
if (controller) {
controller.deactivate();
}
}
/**
* Exposed for testing purposes
*/
export function getController(): MainController {
return controller;
if (controller) {
controller.deactivate();
}
}

View File

@@ -3,315 +3,141 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {IExtensionConstants} from 'extensions-modules/lib/models/contracts/contracts';
import {Runtime, LinuxDistribution} from 'extensions-modules/lib/models/platform';
import { IExtensionConstants } from 'extensions-modules/lib/models/contracts/contracts';
import { Runtime, LinuxDistribution } from 'extensions-modules/lib/models/platform';
// constants
export class Constants implements IExtensionConstants {
public readonly languageId = 'sql';
public readonly extensionName = 'mssql';
public readonly extensionConfigSectionName = 'mssql';
public readonly connectionApplicationName = 'vscode-mssql';
public readonly outputChannelName = 'MSSQL';
public readonly connectionConfigFilename = 'settings.json';
public readonly connectionsArrayName = 'mssql.connections';
public readonly cmdRunQuery = 'extension.runQuery';
public readonly cmdCancelQuery = 'extension.cancelQuery';
public readonly cmdConnect = 'extension.connect';
public readonly cmdDisconnect = 'extension.disconnect';
public readonly cmdChooseDatabase = 'extension.chooseDatabase';
public readonly cmdShowReleaseNotes = 'extension.showReleaseNotes';
public readonly cmdShowGettingStarted = 'extension.showGettingStarted';
public readonly cmdNewQuery = 'extension.newQuery';
public readonly cmdManageConnectionProfiles = 'extension.manageProfiles';
public readonly sqlDbPrefix = '.database.windows.net';
public readonly defaultConnectionTimeout = 15;
public readonly azureSqlDbConnectionTimeout = 30;
public readonly azureDatabase = 'Azure';
public readonly defaultPortNumber = 1433;
public readonly sqlAuthentication = 'SqlLogin';
public readonly defaultDatabase = 'master';
public readonly errorPasswordExpired = 18487;
public readonly errorPasswordNeedsReset = 18488;
public readonly maxDisplayedStatusTextLength = 50;
public readonly outputContentTypeRoot = 'root';
public readonly outputContentTypeMessages = 'messages';
public readonly outputContentTypeResultsetMeta = 'resultsetsMeta';
public readonly outputContentTypeColumns = 'columns';
public readonly outputContentTypeRows = 'rows';
public readonly outputContentTypeConfig = 'config';
public readonly outputContentTypeSaveResults = 'saveResults';
public readonly outputContentTypeOpenLink = 'openLink';
public readonly outputContentTypeCopy = 'copyResults';
public readonly outputContentTypeEditorSelection = 'setEditorSelection';
public readonly outputContentTypeShowError = 'showError';
public readonly outputContentTypeShowWarning = 'showWarning';
public readonly outputServiceLocalhost = 'http://localhost:';
public readonly msgContentProviderSqlOutputHtml = 'dist/html/sqlOutput.ejs';
public readonly contentProviderMinFile = 'dist/js/app.min.js';
public readonly configLogDebugInfo = 'logDebugInfo';
public readonly providerId = 'MSSQL';
public readonly installFolderName = 'sqltoolsservice';
public readonly telemetryExtensionName = 'carbon-mssql';
public readonly languageId = 'sql';
public readonly extensionName = 'mssql';
public readonly extensionConfigSectionName = 'mssql';
public readonly outputChannelName = 'MSSQL';
public readonly providerId = 'MSSQL';
public readonly installFolderName = 'sqltoolsservice';
public readonly telemetryExtensionName = 'carbon-mssql';
// localizable strings
public readonly configMyConnectionsNoServerName = 'Missing server name in user preferences connection: ';
public readonly msgLocalWebserviceStaticContent = 'LocalWebService: added static html content path: ';
public readonly msgLocalWebserviceStarted = 'LocalWebService listening on port ';
public readonly msgRunQueryAllBatchesExecuted = 'runQuery: all batches executed';
public readonly msgStartedExecute = 'Started query execution for document "{0}"';
public readonly msgFinishedExecute = 'Finished query execution for document "{0}"';
public readonly msgRunQueryError = 'runQuery: error: ';
public readonly msgRunQueryExecutingBatch = 'runQuery: executeBatch called with SQL: ';
public readonly msgRunQueryAddBatchResultsets = 'runQuery: adding resultsets for batch: ';
public readonly msgRunQueryAddBatchError = 'runQuery: adding error message for batch: ';
public readonly msgRunQueryConnectionActive = 'runQuery: active connection is connected, using it to run query';
public readonly msgRunQueryConnectionDisconnected = 'runQuery: active connection is disconnected, reconnecting';
public readonly msgRunQueryNoConnection = 'runQuery: no active connection - prompting for user';
public readonly msgRunQueryInProgress = 'A query is already running for this editor session. Please cancel this query or wait for its completion.';
public readonly runQueryBatchStartMessage = 'Started executing query at ';
public readonly runQueryBatchStartLine = 'Line {0}';
public readonly msgCancelQueryFailed = 'Canceling the query failed: {0}';
public readonly msgCancelQueryNotRunning = 'Cannot cancel query as no query is running.';
public readonly msgCancelQuerySuccess = 'Successfully canceled the query.';
public readonly msgContentProviderOnContentUpdated = 'Content provider: onContentUpdated called';
public readonly msgContentProviderAssociationFailure = 'Content provider: Unable to associate status view for current file';
public readonly msgContentProviderOnRootEndpoint = 'LocalWebService: Root end-point called';
public readonly msgContentProviderOnResultsEndpoint = 'LocalWebService: ResultsetsMeta endpoint called';
public readonly msgContentProviderOnMessagesEndpoint = 'LocalWebService: Messages end-point called';
public readonly msgContentProviderOnColumnsEndpoint = 'LocalWebService: Columns end-point called for index = ';
public readonly msgContentProviderOnRowsEndpoint = 'LocalWebService: Rows end-point called for index = ';
public readonly msgContentProviderOnClear = 'Content provider: clear called';
public readonly msgContentProviderOnUpdateContent = 'Content provider: updateContent called';
public readonly msgContentProviderProvideContent = 'Content provider: provideTextDocumentContent called: ';
public readonly msgChooseDatabaseNotConnected = 'No connection was found. Please connect to a server first.';
public readonly msgChooseDatabasePlaceholder = 'Choose a database from the list below';
public readonly msgConnectionError = 'Error {0}: {1}';
public readonly msgConnectionError2 = 'Failed to connect: {0}';
public readonly msgConnectionErrorPasswordExpired = 'Error {0}: {1} Please login as a different user and change the password using ALTER LOGIN.';
public readonly connectionErrorChannelName = 'Connection Errors';
public readonly msgPromptCancelConnect = 'Server connection in progress. Do you want to cancel?';
public readonly msgPromptClearRecentConnections = 'Confirm to clear recent connections list';
public readonly msgOpenSqlFile = 'To use this command, Open a .sql file -or- ' +
'Change editor language to "SQL" -or- ' +
'Select T-SQL text in the active SQL editor.';
public readonly recentConnectionsPlaceholder = 'Choose a connection profile from the list below';
public readonly msgNoConnectionsInSettings = 'To use this command, add connection profile to User Settings.';
public readonly labelOpenGlobalSettings = 'Open Global Settings';
public readonly labelOpenWorkspaceSettings = 'Open Workspace Settings';
public readonly CreateProfileFromConnectionsListLabel = 'Create Connection Profile';
public readonly CreateProfileLabel = 'Create';
public readonly ClearRecentlyUsedLabel = 'Clear Recent Connections List';
public readonly EditProfilesLabel = 'Edit';
public readonly RemoveProfileLabel = 'Remove';
public readonly ManageProfilesPrompt = 'Manage Connection Profiles';
public readonly SampleServerName = '{{put-server-name-here}}';
public readonly serverPrompt = 'Server name';
public readonly serverPlaceholder = 'hostname\\instance or <server>.database.windows.net';
public readonly databasePrompt = 'Database name';
public readonly databasePlaceholder = '[Optional] Database to connect (press Enter to connect to <default> database)';
public readonly databaseDefaultValue = 'master';
public readonly authTypePrompt = 'Authentication Type';
public readonly authTypeIntegrated = 'Integrated';
public readonly authTypeSql = 'SQL Login';
public readonly authTypeAdUniversal = 'Active Directory Universal';
public readonly usernamePrompt = 'User name';
public readonly usernamePlaceholder = 'User name (SQL Login)';
public readonly passwordPrompt = 'Password';
public readonly passwordPlaceholder = 'Password (SQL Login)';
public readonly msgSavePassword = 'Save Password? If \'No\', password will be required each time you connect';
public readonly profileNamePrompt = 'Profile Name';
public readonly profileNamePlaceholder = '[Optional] Enter a name for this profile';
public readonly filepathPrompt = 'File path';
public readonly filepathPlaceholder = 'File name';
public readonly filepathMessage = 'File name';
public readonly overwritePrompt = 'A file with this name already exists. Do you want to replace the existing file?';
public readonly overwritePlaceholder = 'A file with this name already exists';
public readonly msgSaveResultInProgress = 'A save request is already executing. Please wait for its completion.';
public readonly msgCannotOpenContent = 'Error occurred opening content in editor.';
public readonly msgSaveStarted = 'Started saving results to ';
public readonly msgSaveFailed = 'Failed to save results. ';
public readonly msgSaveSucceeded = 'Successfully saved results to ';
public readonly msgSelectProfile = 'Select connection profile';
public readonly msgSelectProfileToRemove = 'Select profile to remove';
public readonly confirmRemoveProfilePrompt = 'Confirm to remove this profile.';
public readonly msgNoProfilesSaved = 'No connection profile to remove.';
public readonly msgProfileRemoved = 'Profile removed successfully';
public readonly msgProfileCreated = 'Profile created successfully';
public readonly msgProfileCreatedAndConnected = 'Profile created and connected';
public readonly msgClearedRecentConnections = 'Recent connections list cleared';
public readonly msgSelectionIsRequired = 'Selection is required.';
public readonly msgIsRequired = ' is required.';
public readonly msgRetry = 'Retry';
public readonly msgError = 'Error: ';
public readonly msgYes = 'Yes';
public readonly msgNo = 'No';
public readonly defaultDatabaseLabel = '<default>';
public readonly notConnectedLabel = 'Disconnected';
public readonly notConnectedTooltip = 'Click to connect to a database';
public readonly connectingLabel = 'Connecting';
public readonly connectingTooltip = 'Connecting to: ';
public readonly connectedLabel = 'Connected.';
public readonly connectErrorLabel = 'Connection error';
public readonly connectErrorTooltip = 'Error connecting to: ';
public readonly connectErrorCode = 'Errorcode: ';
public readonly connectErrorMessage = 'ErrorMessage: ';
public readonly executeQueryLabel = 'Executing query ';
public readonly cancelingQueryLabel = 'Canceling query ';
public readonly updatingIntelliSenseLabel = 'Updating IntelliSense...';
public readonly unfoundResult = 'Data was disposed when text editor was closed; to view data please reexecute query.';
public readonly serviceCompatibleVersion = '1.0.0';
public readonly serviceNotCompatibleError = 'Client is not compatible with the service layer';
public readonly serviceInstallingTo = 'Installing SQL tools service to';
public readonly serviceInitializing = 'Initializing SQL tools service for the mssql extension.';
public readonly commandsNotAvailableWhileInstallingTheService = 'Note: mssql commands will be available after installing the service.';
public readonly serviceInstalled = 'Sql Tools Service installed';
public readonly serviceInstallationFailed = 'Failed to install Sql Tools Service';
public readonly serviceLoadingFailed = 'Failed to load Sql Tools Service';
public readonly invalidServiceFilePath = 'Invalid file path for Sql Tools Service';
public readonly extensionNotInitializedError = 'Unable to execute the command while the extension is initializing. Please try again later.';
public readonly untitledScheme = 'untitled';
public readonly untitledSaveTimeThreshold = 10.0;
public readonly renamedOpenTimeThreshold = 10.0;
public readonly msgChangeLanguageMode = 'To use this command, you must set the language to \"SQL\". Confirm to change language mode.';
public readonly timeToWaitForLanguageModeChange = 10000.0;
public readonly msgChangedDatabaseContext = 'Changed database context to \"{0}\" for document \"{1}\"';
public readonly msgPromptRetryCreateProfile = 'Error: Unable to connect using the connection information provided. Retry profile creation?';
public readonly retryLabel = 'Retry';
public readonly msgConnecting = 'Connecting to server \"{0}\" on document \"{1}\".';
public readonly msgConnectedServerInfo = 'Connected to server \"{0}\" on document \"{1}\". Server information: {2}';
public readonly msgConnectionFailed = 'Error connecting to server \"{0}\". Details: {1}';
public readonly msgChangingDatabase = 'Changing database context to \"{0}\" on server \"{1}\" on document \"{2}\".';
public readonly msgChangedDatabase = 'Changed database context to \"{0}\" on server \"{1}\" on document \"{2}\".';
public readonly msgDisconnected = 'Disconnected on document \"{0}\"';
public readonly msgErrorReadingConfigFile = 'Error: Unable to load connection profiles from [{0}]. Check if the file is formatted correctly.';
public readonly msgErrorOpeningConfigFile = 'Error: Unable to open connection profile settings file.';
public readonly extConfigResultKeys = ['shortcuts', 'messagesDefaultOpen'];
public readonly extConfigResultFontFamily = 'resultsFontFamily';
public readonly extConfigResultFontSize = 'resultsFontSize';
public readonly titleResultsPane = 'Results: {0}';
public readonly macOpenSslErrorMessage = `OpenSSL version >=1.0.1 is required to connect.`;
public readonly macOpenSslHelpButton = 'Help';
public readonly macOpenSslHelpLink = 'https://github.com/Microsoft/vscode-mssql/wiki/OpenSSL-Configuration';
public readonly serviceName = 'SQLToolsService';
public readonly serviceInitializingOutputChannelName = 'SqlToolsService Initialization';
public readonly gettingStartedGuideLink = 'https://aka.ms/mssql-getting-started';
public readonly serviceCrashMessage = 'SQL Tools Service component exited unexpectedly. Please restart SQL Operations Studio.';
public readonly serviceCrashLink = 'https://github.com/Microsoft/vscode-mssql/wiki/SqlToolsService-Known-Issues';
public readonly gettingDefinitionMessage = 'Getting definition ...';
public readonly definitionRequestedStatus = 'DefinitionRequested';
public readonly definitionRequestCompletedStatus = 'DefinitionRequestCompleted';
public readonly updatingIntelliSenseStatus = 'updatingIntelliSense';
public readonly intelliSenseUpdatedStatus = 'intelliSenseUpdated';
// localizable strings
public readonly serviceCompatibleVersion = '1.0.0';
public readonly serviceInstallingTo = 'Installing SQL tools service to';
public readonly serviceInitializing = 'Initializing SQL tools service for the mssql extension.';
public readonly commandsNotAvailableWhileInstallingTheService = 'Note: mssql commands will be available after installing the service.';
public readonly serviceInstalled = 'Sql Tools Service installed';
public readonly serviceInstallationFailed = 'Failed to install Sql Tools Service';
public readonly serviceLoadingFailed = 'Failed to load Sql Tools Service';
public readonly invalidServiceFilePath = 'Invalid file path for Sql Tools Service';
public readonly serviceName = 'SQLToolsService';
public readonly serviceInitializingOutputChannelName = 'SqlToolsService Initialization';
public readonly serviceCrashMessage = 'SQL Tools Service component exited unexpectedly. Please restart SQL Operations Studio.';
public readonly serviceCrashLink = 'https://github.com/Microsoft/vscode-mssql/wiki/SqlToolsService-Known-Issues';
/**
* Returns a supported .NET Core Runtime ID (RID) for the current platform. The list of Runtime IDs
* is available at https://github.com/dotnet/corefx/tree/master/pkg/Microsoft.NETCore.Platforms.
*/
public getRuntimeId(platform: string, architecture: string, distribution: LinuxDistribution): Runtime {
switch (platform) {
case 'win32':
switch (architecture) {
case 'x86': return Runtime.Windows_86;
case 'x86_64': return Runtime.Windows_64;
default:
}
/**
* Returns a supported .NET Core Runtime ID (RID) for the current platform. The list of Runtime IDs
* is available at https://github.com/dotnet/corefx/tree/master/pkg/Microsoft.NETCore.Platforms.
*/
public getRuntimeId(platform: string, architecture: string, distribution: LinuxDistribution): Runtime {
switch (platform) {
case 'win32':
switch (architecture) {
case 'x86': return Runtime.Windows_86;
case 'x86_64': return Runtime.Windows_64;
default:
}
throw new Error(`Unsupported Windows architecture: ${architecture}`);
throw new Error(`Unsupported Windows architecture: ${architecture}`);
case 'darwin':
if (architecture === 'x86_64') {
// Note: We return the El Capitan RID for Sierra
return Runtime.OSX;
}
case 'darwin':
if (architecture === 'x86_64') {
// Note: We return the El Capitan RID for Sierra
return Runtime.OSX;
}
throw new Error(`Unsupported macOS architecture: ${architecture}`);
throw new Error(`Unsupported macOS architecture: ${architecture}`);
case 'linux':
if (architecture === 'x86_64') {
case 'linux':
if (architecture === 'x86_64') {
// First try the distribution name
let runtimeId = Constants.getRuntimeIdHelper(distribution.name, distribution.version);
// First try the distribution name
let runtimeId = Constants.getRuntimeIdHelper(distribution.name, distribution.version);
// If the distribution isn't one that we understand, but the 'ID_LIKE' field has something that we understand, use that
//
// NOTE: 'ID_LIKE' doesn't specify the version of the 'like' OS. So we will use the 'VERSION_ID' value. This will restrict
// how useful ID_LIKE will be since it requires the version numbers to match up, but it is the best we can do.
if (runtimeId === Runtime.UnknownRuntime && distribution.idLike && distribution.idLike.length > 0) {
for (let id of distribution.idLike) {
runtimeId = Constants.getRuntimeIdHelper(id, distribution.version);
if (runtimeId !== Runtime.UnknownRuntime) {
break;
}
}
}
// If the distribution isn't one that we understand, but the 'ID_LIKE' field has something that we understand, use that
//
// NOTE: 'ID_LIKE' doesn't specify the version of the 'like' OS. So we will use the 'VERSION_ID' value. This will restrict
// how useful ID_LIKE will be since it requires the version numbers to match up, but it is the best we can do.
if (runtimeId === Runtime.UnknownRuntime && distribution.idLike && distribution.idLike.length > 0) {
for (let id of distribution.idLike) {
runtimeId = Constants.getRuntimeIdHelper(id, distribution.version);
if (runtimeId !== Runtime.UnknownRuntime) {
break;
}
}
}
if (runtimeId !== Runtime.UnknownRuntime && runtimeId !== Runtime.UnknownVersion) {
return runtimeId;
}
}
if (runtimeId !== Runtime.UnknownRuntime && runtimeId !== Runtime.UnknownVersion) {
return runtimeId;
}
}
// If we got here, this is not a Linux distro or architecture that we currently support.
throw new Error(`Unsupported Linux distro: ${distribution.name}, ${distribution.version}, ${architecture}`);
default :
// If we got here, we've ended up with a platform we don't support like 'freebsd' or 'sunos'.
// Chances are, VS Code doesn't support these platforms either.
throw Error('Unsupported platform ' + platform);
}
}
// If we got here, this is not a Linux distro or architecture that we currently support.
throw new Error(`Unsupported Linux distro: ${distribution.name}, ${distribution.version}, ${architecture}`);
default:
// If we got here, we've ended up with a platform we don't support like 'freebsd' or 'sunos'.
// Chances are, VS Code doesn't support these platforms either.
throw Error('Unsupported platform ' + platform);
}
}
private static getRuntimeIdHelper(distributionName: string, distributionVersion: string): Runtime {
switch (distributionName) {
case 'ubuntu':
if (distributionVersion.startsWith('14')) {
// This also works for Linux Mint
return Runtime.Ubuntu_14;
} else if (distributionVersion.startsWith('16')) {
return Runtime.Ubuntu_16;
}
private static getRuntimeIdHelper(distributionName: string, distributionVersion: string): Runtime {
switch (distributionName) {
case 'ubuntu':
if (distributionVersion.startsWith('14')) {
// This also works for Linux Mint
return Runtime.Ubuntu_14;
} else if (distributionVersion.startsWith('16')) {
return Runtime.Ubuntu_16;
}
break;
case 'elementary':
case 'elementary OS':
if (distributionVersion.startsWith('0.3')) {
// Elementary OS 0.3 Freya is binary compatible with Ubuntu 14.04
return Runtime.Ubuntu_14;
} else if (distributionVersion.startsWith('0.4')) {
// Elementary OS 0.4 Loki is binary compatible with Ubuntu 16.04
return Runtime.Ubuntu_16;
}
break;
case 'elementary':
case 'elementary OS':
if (distributionVersion.startsWith('0.3')) {
// Elementary OS 0.3 Freya is binary compatible with Ubuntu 14.04
return Runtime.Ubuntu_14;
} else if (distributionVersion.startsWith('0.4')) {
// Elementary OS 0.4 Loki is binary compatible with Ubuntu 16.04
return Runtime.Ubuntu_16;
}
break;
case 'linuxmint':
if (distributionVersion.startsWith('18')) {
// Linux Mint 18 is binary compatible with Ubuntu 16.04
return Runtime.Ubuntu_16;
}
break;
case 'linuxmint':
if (distributionVersion.startsWith('18')) {
// Linux Mint 18 is binary compatible with Ubuntu 16.04
return Runtime.Ubuntu_16;
}
break;
case 'centos':
case 'ol':
// Oracle Linux is binary compatible with CentOS
return Runtime.CentOS_7;
case 'fedora':
return Runtime.Fedora_23;
case 'opensuse':
return Runtime.OpenSUSE_13_2;
case 'sles':
return Runtime.SLES_12_2;
case 'rhel':
return Runtime.RHEL_7;
case 'debian':
return Runtime.Debian_8;
case 'galliumos':
if (distributionVersion.startsWith('2.0')) {
return Runtime.Ubuntu_16;
}
break;
default:
return Runtime.UnknownRuntime;
}
break;
case 'centos':
case 'ol':
// Oracle Linux is binary compatible with CentOS
return Runtime.CentOS_7;
case 'fedora':
return Runtime.Fedora_23;
case 'opensuse':
return Runtime.OpenSUSE_13_2;
case 'sles':
return Runtime.SLES_12_2;
case 'rhel':
return Runtime.RHEL_7;
case 'debian':
return Runtime.Debian_8;
case 'galliumos':
if (distributionVersion.startsWith('2.0')) {
return Runtime.Ubuntu_16;
}
break;
default:
return Runtime.UnknownRuntime;
}
return Runtime.UnknownVersion;
}
return Runtime.UnknownVersion;
}
}

View File

@@ -1,10 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
* 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 'dataprotocol-client';
import { RequestType } from 'dataprotocol-client';
import * as data from 'data';
// DEV-NOTE: Still finalizing what we'll need as part of this interface
@@ -16,37 +16,35 @@ import * as data from 'data';
* @param {boolean} appendToFile Whether we should append or overwrite the file in savePath
*/
export class SaveResultsInfo {
constructor(public saveFormat: string, public savePath: string, public results: string,
public appendToFile: boolean) {
}
constructor(public saveFormat: string, public savePath: string, public results: string,
public appendToFile: boolean) {
}
}
export namespace SaveAsRequest {
export const type: RequestType<SaveResultsInfo, data.SaveResultRequestResult, void> = { get method(): string { return 'query/saveAs'; } };
export const type: RequestType<SaveResultsInfo, data.SaveResultRequestResult, void> = { get method(): string { return 'query/saveAs'; } };
}
// --------------------------------- < Read Credential Request > -------------------------------------------------
// Read Credential request message callback declaration
export namespace ReadCredentialRequest {
export const type: RequestType<Credential, Credential, void> = { get method(): string { return 'credential/read'; } };
export const type: RequestType<Credential, Credential, void> = { get method(): string { return 'credential/read'; } };
}
/**
* Parameters to initialize a connection to a database
*/
export class Credential {
/**
* Unique ID identifying the credential
*/
public credentialId: string;
/**
* Unique ID identifying the credential
*/
public credentialId: string;
/**
* password
*/
public password: string;
/**
* password
*/
public password: string;
}
// --------------------------------- </ Read Credential Request > -------------------------------------------------
@@ -55,7 +53,7 @@ export class Credential {
// Save Credential request message callback declaration
export namespace SaveCredentialRequest {
export const type: RequestType<Credential, boolean, void> = { get method(): string { return 'credential/save'; } };
export const type: RequestType<Credential, boolean, void> = { get method(): string { return 'credential/save'; } };
}
// --------------------------------- </ Save Credential Request > -------------------------------------------------
@@ -64,7 +62,7 @@ export namespace SaveCredentialRequest {
// Delete Credential request message callback declaration
export namespace DeleteCredentialRequest {
export const type: RequestType<Credential, boolean, void> = { get method(): string { return 'credential/delete'; } };
export const type: RequestType<Credential, boolean, void> = { get method(): string { return 'credential/delete'; } };
}
// --------------------------------- </ Delete Credential Request > -------------------------------------------------
@@ -101,4 +99,3 @@ export interface HandleFirewallRuleResponse {
result: boolean;
ipAddress: string;
}

View File

@@ -1,13 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
* 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 Contracts from '../models/contracts';
import { SqlToolsServiceClient } from 'extensions-modules';
import { LanguageClient } from 'dataprotocol-client';
import * as data from 'data';
import * as path from 'path';
/**
@@ -17,47 +18,47 @@ import * as data from 'data';
*/
export class AzureResourceProvider implements data.ResourceProvider {
public languageClient: LanguageClient;
public languageClient: LanguageClient;
constructor(private _client?: SqlToolsServiceClient, langClient?: LanguageClient) {
if (!this._client) {
this._client = SqlToolsServiceClient.instance;
}
this.languageClient = langClient;
}
constructor(private _client?: SqlToolsServiceClient, langClient?: LanguageClient) {
if (!this._client) {
this._client = SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json'));
}
this.languageClient = langClient;
}
public createFirewallRule(account: data.Account, firewallruleInfo: data.FirewallRuleInfo): Thenable<data.CreateFirewallRuleResponse> {
let self = this;
return new Promise<data.CreateFirewallRuleResponse>((resolve, reject) => {
self._client.
sendRequest(Contracts.CreateFirewallRuleRequest.type, self.asCreateFirewallRuleParams(account, firewallruleInfo), self.languageClient)
.then(response => {
resolve(response);
}, err => reject(err));
});
}
public createFirewallRule(account: data.Account, firewallruleInfo: data.FirewallRuleInfo): Thenable<data.CreateFirewallRuleResponse> {
let self = this;
return new Promise<data.CreateFirewallRuleResponse>((resolve, reject) => {
self._client.
sendRequest(Contracts.CreateFirewallRuleRequest.type, self.asCreateFirewallRuleParams(account, firewallruleInfo), self.languageClient)
.then(response => {
resolve(response);
}, err => reject(err));
});
}
public handleFirewallRule(errorCode: number, errorMessage: string, connectionTypeId: string): Thenable<data.HandleFirewallRuleResponse> {
let self = this;
return new Promise<data.HandleFirewallRuleResponse>((resolve, reject) => {
let params: Contracts.HandleFirewallRuleParams = { errorCode: errorCode, errorMessage: errorMessage, connectionTypeId: connectionTypeId };
public handleFirewallRule(errorCode: number, errorMessage: string, connectionTypeId: string): Thenable<data.HandleFirewallRuleResponse> {
let self = this;
return new Promise<data.HandleFirewallRuleResponse>((resolve, reject) => {
let params: Contracts.HandleFirewallRuleParams = { errorCode: errorCode, errorMessage: errorMessage, connectionTypeId: connectionTypeId };
self._client.
sendRequest(Contracts.HandleFirewallRuleRequest.type, params, self.languageClient)
.then(response => {
resolve(response);
}, err => reject(err));
});
}
self._client.
sendRequest(Contracts.HandleFirewallRuleRequest.type, params, self.languageClient)
.then(response => {
resolve(response);
}, err => reject(err));
});
}
private asCreateFirewallRuleParams(account: data.Account, params: data.FirewallRuleInfo): Contracts.CreateFirewallRuleParams {
return {
account: account,
serverName: params.serverName,
startIpAddress: params.startIpAddress,
endIpAddress: params.endIpAddress,
securityTokenMappings: params.securityTokenMappings
};
}
private asCreateFirewallRuleParams(account: data.Account, params: data.FirewallRuleInfo): Contracts.CreateFirewallRuleParams {
return {
account: account,
serverName: params.serverName,
startIpAddress: params.startIpAddress,
endIpAddress: params.endIpAddress,
securityTokenMappings: params.securityTokenMappings
};
}
}

View File

@@ -1,10 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
* 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 { SaveResultsInfo } from '../models/contracts';
import * as data from 'data';
/**
@@ -14,5 +13,5 @@ import * as data from 'data';
* @interface ISerialization
*/
export interface ISerialization {
saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Promise<data.SaveResultRequestResult>;
saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Promise<data.SaveResultRequestResult>;
}

View File

@@ -1,7 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
* 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 Contracts from '../models/contracts';
@@ -9,33 +9,34 @@ import { ISerialization } from './iserialization';
import { SqlToolsServiceClient } from 'extensions-modules';
import * as data from 'data';
import { LanguageClient } from 'dataprotocol-client';
import * as path from 'path';
/**
* Implements serializer for query results
*/
export class Serialization implements ISerialization {
constructor(private _client?: SqlToolsServiceClient, private _languageClient?: LanguageClient) {
if (!this._client) {
this._client = SqlToolsServiceClient.instance;
}
}
constructor(private _client?: SqlToolsServiceClient, private _languageClient?: LanguageClient) {
if (!this._client) {
this._client = SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json'));
}
}
/**
* Saves results as a specified path
*
* @param {string} credentialId the ID uniquely identifying this credential
* @returns {Promise<ISaveResultsInfo>} Promise that resolved to the credential, or undefined if not found
*/
public saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Promise<data.SaveResultRequestResult> {
let self = this;
let resultsInfo: Contracts.SaveResultsInfo = new Contracts.SaveResultsInfo(saveFormat, savePath, results, appendToFile);
return new Promise<data.SaveResultRequestResult>( (resolve, reject) => {
self._client
.sendRequest(Contracts.SaveAsRequest.type, resultsInfo, this._languageClient)
.then(result => {
resolve(<data.SaveResultRequestResult>result);
}, err => reject(err));
});
}
/**
* Saves results as a specified path
*
* @param {string} credentialId the ID uniquely identifying this credential
* @returns {Promise<ISaveResultsInfo>} Promise that resolved to the credential, or undefined if not found
*/
public saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Promise<data.SaveResultRequestResult> {
let self = this;
let resultsInfo: Contracts.SaveResultsInfo = new Contracts.SaveResultsInfo(saveFormat, savePath, results, appendToFile);
return new Promise<data.SaveResultRequestResult>((resolve, reject) => {
self._client
.sendRequest(Contracts.SaveAsRequest.type, resultsInfo, this._languageClient)
.then(result => {
resolve(<data.SaveResultRequestResult>result);
}, err => reject(err));
});
}
}

View File

@@ -4,4 +4,5 @@
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../../src/sql/data.d.ts'/>
/// <reference path='../../../../../src/sql/data.d.ts'/>
/// <reference types='@types/node'/>

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
{
"accounts.azure.enableUsGovCloud": {
"type": "boolean",
"default": false,
"description": "%config.enableUsGovCloudDescription%"
},
"accounts.azure.enableChinaCloud": {
"type": "boolean",
"default": false,
"description": "%config.enableChinaCloudDescription%"
},
"accounts.azure.enableGermanyCloud": {
"type": "boolean",
"default": false,
"description": "%config.enableGermanyCloudDescription%"
}
}

View File

@@ -9,13 +9,14 @@
"activationEvents": [
"*"
],
"main": "./client/out/mssqlMain",
"main": "./client/out/main",
"extensionDependencies": [
"vscode.sql"
],
"scripts": {
"compile": "gulp compile-extension:mssql-client",
"postinstall": "node ./node_modules/vscode/bin/install"
"postinstall": "node ./node_modules/vscode/bin/install",
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
},
"contributes": {
"languages": [
@@ -26,15 +27,7 @@
],
"aliases": [
"SQL"
],
"configuration": "./syntaxes/sql.configuration.json"
}
],
"grammars": [
{
"language": "sql",
"scopeName": "source.sql",
"path": "./syntaxes/SQL.plist"
]
}
],
"outputChannels": [
@@ -46,38 +39,10 @@
"path": "./snippets/mssql.json"
}
],
"commands": [
{
"command": "extension.clearTokenCache",
"title": "%extension.clearTokenCache%",
"category": "Azure Accounts"
}
],
"account-type": [
{
"id": "microsoft",
"icon": {
"light": "./out/account-provider/media/microsoft_account_light.svg",
"dark": "./out/account-provider/media/microsoft_account_dark.svg"
}
},
{
"id": "work_school",
"icon": {
"light": "./out/account-provider/media/work_school_account_light.svg",
"dark": "./out/account-provider/media/work_school_account_dark.svg"
}
}
],
"configuration": {
"type": "object",
"title": "MSSQL configuration",
"properties": {
"accounts.azure.enablePublicCloud": {
"type": "boolean",
"default": true,
"description": "%config.enablePublicCloudDescription%"
},
"mssql.query.displayBitAsNumber": {
"type": "boolean",
"default": true,
@@ -227,18 +192,9 @@
}
},
"dependencies": {
"dataprotocol-client": "file:../../dataprotocol-node/client",
"extensions-modules": "file:../../extensions-modules",
"adal-node": "0.1.25",
"decompress": "^4.0.0",
"fs-extra-promise": "^1.0.1",
"opener": "1.4.3",
"request": "2.63.0",
"vscode-extension-telemetry": "^0.0.8",
"vscode-nls": "2.0.2"
"dataprotocol-client": "file:../../dataprotocol-node/client"
},
"devDependencies": {
"vscode": "1.0.1",
"@types/node": "^8.0.24"
"vscode": "1.0.1"
}
}

View File

@@ -1,7 +1,8 @@
{
"extension.clearTokenCache": "Clear Azure Account Token Cache",
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
"config.enableGermanyCloudDescription": "Should Azure Germany integration be enabled"
"json.schemas.desc": "Associate schemas to JSON files in the current project",
"json.schemas.url.desc": "A URL to a schema or a relative path to a schema in the current directory",
"json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas.",
"json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.",
"json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.",
"json.format.enable.desc": "Enable/disable default JSON formatter (requires restart)"
}

View File

@@ -208,10 +208,10 @@
"body": [
"-- Get a list of tables and views in the current database",
"SELECT table_catalog [database], table_schema [schema], table_name name, table_type type",
"FROM INFORMATION_SCHEMA.TABLES",
"FROM information_schema.tables",
"GO"
],
"description": "List tables and views in the current database"
"description": "List tables and vies in the current database"
},
"List databases": {
@@ -229,15 +229,15 @@
"body": [
"-- List columns in all tables whose name is like '${1:TableName}'",
"SELECT ",
"\tTableName = tbl.TABLE_SCHEMA + '.' + tbl.TABLE_NAME, ",
"\tColumnName = col.COLUMN_NAME, ",
"\tColumnDataType = col.DATA_TYPE",
"FROM INFORMATION_SCHEMA.TABLES tbl",
"INNER JOIN INFORMATION_SCHEMA.COLUMNS col ",
"\tON col.TABLE_NAME = tbl.TABLE_NAME",
"\tAND col.TABLE_SCHEMA = tbl.TABLE_SCHEMA",
"\tTableName = tbl.table_schema + '.' + tbl.table_name, ",
"\tColumnName = col.column_name, ",
"\tColumnDataType = col.data_type",
"FROM information_schema.tables tbl",
"INNER JOIN information_schema.columns col ",
"\tON col.table_name = tbl.table_name",
"\tAND col.table_schema = tbl.table_schema",
"",
"WHERE tbl.TABLE_TYPE = 'BASE TABLE' and tbl.TABLE_NAME like '%${1:TableName}%'",
"WHERE tbl.table_type = 'base table' and tbl.table_name like '%${1:TableName}%'",
"GO"
],
"description": "Lists all the columns and their types for tables matching a LIKE statement"

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +0,0 @@
{
}