Enforce vscode and ads version check when installing extensions (#4267)

* engine check when install extension

* gallery install/update and vsix install

* FIX COMMENTS

* Fix the detail not loading issue when version is invalid

* add more comments and adress PR comments

* add install telemetry for install from vsix scenario

* correct the name of the version property for telemetry
This commit is contained in:
Alan Ren
2019-03-07 13:04:50 -08:00
committed by GitHub
parent c8bde41451
commit 9e1f04e476
19 changed files with 143 additions and 73 deletions

View File

@@ -6,7 +6,7 @@
"publisher": "Microsoft",
"engines": {
"vscode": "*",
"sqlops": "*"
"azdata": "*"
},
"extensionPack": [
"Microsoft.agent",

View File

@@ -7,7 +7,7 @@
"preview": true,
"engines": {
"vscode": "^1.25.0",
"sqlops": "*"
"azdata": "*"
},
"activationEvents": [
"*"

View File

@@ -9,7 +9,8 @@
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"engines": {
"vscode": "0.10.x"
"vscode": "*",
"azdata": "^1.4.0"
},
"activationEvents": [
"*"
@@ -27,51 +28,51 @@
"type": "object",
"title": "Kubernetes configuration",
"properties": {
"mssql-bdc": {
"type": "object",
"description": "Kubernetes configuration",
"properties": {
"mssql-bdc.kubectl-path": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.windows": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.mac": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.linux": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubeconfig": {
"type": "string",
"description": "File path to the kubeconfig file."
},
"mssql-bdc.knownKubeconfigs": {
"type": "array",
"description": "File paths to kubeconfig files from which you can select."
},
"mssql-bdc.outputFormat": {
"enum": [
"json",
"yaml"
],
"type": "string",
"description": "Output format for Kubernetes specs. One of 'json' or 'yaml' (default)."
}
},
"default": {
"mssql-bdc.namespace": "",
"mssql-bdc.kubectl-path": "",
"mssql-bdc.kubeconfig": "",
"mssql-bdc.knownKubeconfigs": []
}
"mssql-bdc": {
"type": "object",
"description": "Kubernetes configuration",
"properties": {
"mssql-bdc.kubectl-path": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.windows": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.mac": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubectl-path.linux": {
"type": "string",
"description": "File path to a kubectl binary."
},
"mssql-bdc.kubeconfig": {
"type": "string",
"description": "File path to the kubeconfig file."
},
"mssql-bdc.knownKubeconfigs": {
"type": "array",
"description": "File paths to kubeconfig files from which you can select."
},
"mssql-bdc.outputFormat": {
"enum": [
"json",
"yaml"
],
"type": "string",
"description": "Output format for Kubernetes specs. One of 'json' or 'yaml' (default)."
}
},
"default": {
"mssql-bdc.namespace": "",
"mssql-bdc.kubectl-path": "",
"mssql-bdc.kubeconfig": "",
"mssql-bdc.knownKubeconfigs": []
}
}
}
},
"commands": [
{
@@ -83,7 +84,8 @@
},
"dependencies": {
"vscode-nls": "^3.2.1",
"download": "^6.2.5"
"download": "^6.2.5",
"shelljs": "^0.8.3"
},
"devDependencies": {
"mocha-junit-reporter": "^1.17.0",

View File

@@ -7,7 +7,7 @@
"preview": true,
"engines": {
"vscode": "^1.25.0",
"sqlops": "*"
"azdata": "*"
},
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx",
"icon": "images/sqlserver.png",

View File

@@ -6,7 +6,7 @@
"private": true,
"engines": {
"vscode": "*",
"sqlops": "*"
"azdata": "*"
},
"activationEvents": [
"*"

View File

@@ -6,7 +6,7 @@
"publisher": "Microsoft",
"engines": {
"vscode": "*",
"sqlops": "*"
"azdata": "*"
},
"main": "./out/extension",
"activationEvents": [

View File

@@ -6,7 +6,7 @@
"publisher": "Microsoft",
"engines": {
"vscode": "^1.26.0",
"sqlops": "*"
"azdata": "*"
},
"license": "SEE LICENSE IN LICENSE.txt",
"repository": "https://github.com/Microsoft/azuredatastudio",

View File

@@ -7,7 +7,7 @@
"preview": true,
"engines": {
"vscode": "^1.26.0",
"sqlops": "*"
"azdata": "*"
},
"icon": "images/sqlserver.png",
"license": "SEE LICENSE IN LICENSE.txt",

View File

@@ -7,7 +7,7 @@
"preview": true,
"engines": {
"vscode": "^1.26.0",
"sqlops": "*"
"azdata": "*"
},
"icon": "images/sqlserver.png",
"license": "SEE LICENSE IN LICENSE.txt",

View File

@@ -6,7 +6,7 @@
"publisher": "demo",
"engines": {
"vscode": "^1.26.0",
"sqlops": "*"
"azdata": "*"
},
"categories": [
"Other"

View File

@@ -117,7 +117,7 @@ export interface IExtensionManifest {
publisher: string;
version: string;
// {{SQL CARBON EDIT}}
engines: { vscode: string; sqlops?: string };
engines: { vscode: string; azdata?: string };
displayName?: string;
description?: string;
main?: string;
@@ -141,6 +141,8 @@ export interface IGalleryExtensionProperties {
dependencies?: string[];
extensionPack?: string[];
engine?: string;
// {{SQL CARBON EDIT}}
azDataEngine?: string;
localizedLanguages?: string[];
}

View File

@@ -71,7 +71,9 @@ export function getLocalExtensionTelemetryData(extension: ILocalExtension): any
publisherId: extension.metadata ? extension.metadata.publisherId : null,
publisherName: extension.manifest.publisher,
publisherDisplayName: extension.metadata ? extension.metadata.publisherDisplayName : null,
dependencies: extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length > 0
dependencies: extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length > 0,
// {{SQL CARBON EDIT}}
extensionVersion: extension.manifest.version
};
}
@@ -99,6 +101,8 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension):
publisherName: extension.publisher,
publisherDisplayName: extension.publisherDisplayName,
dependencies: !!(extension.properties.dependencies && extension.properties.dependencies.length > 0),
// {{SQL CARBON EDIT}}
extensionVersion: extension.version,
...extension.telemetryData
};
}

View File

@@ -123,6 +123,8 @@ const PropertyType = {
Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies',
ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack',
Engine: 'Microsoft.VisualStudio.Code.Engine',
// {{SQL CARBON EDIT}}
AzDataEngine: 'Microsoft.AzDataEngine',
LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages'
};
@@ -298,6 +300,12 @@ function getEngine(version: IRawGalleryExtensionVersion): string {
return (values.length > 0 && values[0].value) || '';
}
// {{SQL CARBON EDIT}}
function getAzureDataStudioEngine(version: IRawGalleryExtensionVersion): string {
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.AzDataEngine) : [];
return (values.length > 0 && values[0].value) || '';
}
function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] {
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : [];
const value = (values.length > 0 && values[0].value) || '';
@@ -343,6 +351,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
dependencies: getExtensions(version, PropertyType.Dependency),
extensionPack: getExtensions(version, PropertyType.ExtensionPack),
engine: getEngine(version),
// {{SQL CARBON EDIT}}
azDataEngine: getAzureDataStudioEngine(version),
localizedLanguages: getLocalizedLanguages(version)
},
/* __GDPR__FRAGMENT__
@@ -724,7 +734,12 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
loadCompatibleVersion(extension: IGalleryExtension, fromVersion: string = extension.version): Promise<IGalleryExtension> {
if (extension.version === fromVersion && extension.properties.engine && isEngineValid(extension.properties.engine)) {
// {{SQL CARBON EDIT}}
// Change to original version: removed the extension version validation
// Reason: This method is used to find the matching gallery extension for the locally installed extension,
// since we only have one entry for each extension (not in-scope to enable mutiple version support for now),
// if the new version of extension is not compatible, the extension won't be displayed properly.
if (extension.version === fromVersion) {
return Promise.resolve(extension);
}
const query = new Query()

View File

@@ -46,6 +46,9 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
// {{SQL CARBON EDIT}
import product from 'vs/platform/node/product';
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled';
@@ -188,6 +191,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
install(vsix: URI, type: LocalExtensionType = LocalExtensionType.User): Promise<IExtensionIdentifier> {
// {{SQL CARBON EDIT}}
let startTime = new Date().getTime();
this.logService.trace('ExtensionManagementService#install', vsix.toString());
return createCancelablePromise(token => {
return this.downloadVsix(vsix)
@@ -197,10 +203,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
return getManifest(zipPath)
.then(manifest => {
const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
// {{SQL CARBON EDIT - Remove VS Code version check}}
// if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
// return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", identifier.id, pkg.version)));
// }
// {{SQL CARBON EDIT - Check VSCode and ADS version}}
if (manifest.engines && (!isEngineValid(manifest.engines.vscode, product.vscodeVersion)
|| (manifest.engines.azdata && !isEngineValid(manifest.engines.azdata, pkg.version)))) {
return Promise.reject(new Error(nls.localize('incompatible', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", identifier.id, pkg.version, manifest.version)));
}
return this.removeIfExists(identifier.id)
.then(
() => {
@@ -216,10 +223,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
// {{SQL CARBON EDIT}}
// Until there's a gallery for SQL Ops Studio, skip retrieving the metadata from the gallery
return this.installExtension({ zipPath, id: identifier.id, metadata: null }, type, token)
.then(
local => this._onDidInstallExtension.fire({ identifier, zipPath, local, operation: InstallOperation.Install }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error, operation: InstallOperation.Install }); return Promise.reject(error); }
);
.then(
local => {
this.reportTelemetry(this.getTelemetryEvent(InstallOperation.Install), getLocalExtensionTelemetryData(local), new Date().getTime() - startTime, void 0);
this._onDidInstallExtension.fire({ identifier, zipPath, local, operation: InstallOperation.Install });
},
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error, operation: InstallOperation.Install }); return Promise.reject(error); }
);
// return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name))
// .then(
// metadata => this.installFromZipPath(identifier, zipPath, metadata, type, token),

View File

@@ -209,7 +209,7 @@ export interface IReducedExtensionDescription {
engines: {
vscode: string;
// {{SQL CARBON EDIT}}
sqlops?: string;
azdata?: string;
};
main?: string;
}
@@ -222,12 +222,13 @@ export function isValidExtensionVersion(version: string, extensionDesc: IReduced
}
// {{SQL CARBON EDIT}}
return (extensionDesc.engines.sqlops && extensionDesc.engines.sqlops === '*') || isVersionValid(version, extensionDesc.engines.sqlops, notices);
return (extensionDesc.engines.azdata && extensionDesc.engines.azdata === '*') || isVersionValid(version, extensionDesc.engines.azdata, notices);
}
export function isEngineValid(engine: string): boolean {
// {{SQL CARBON EDIT}}
export function isEngineValid(engine: string, version: string = pkg.version): boolean {
// TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version
return engine === '*' || isVersionValid(pkg.version, engine);
return engine === '*' || isVersionValid(version, engine);
}
export function isVersionValid(currentVersion: string, requestedVersion: string, notices: string[] = []): boolean {

View File

@@ -191,6 +191,13 @@ export class InstallAction extends Action {
return this.notificationService.error(err);
}
// {{SQL CARBON EDIT}}
// Prompt the user that the current ADS version is not compatible with the extension,
// return here as in this scenario it doesn't make sense for the user to download manually.
if(err && err.code === INSTALL_ERROR_INCOMPATIBLE) {
return this.notificationService.error(err);
}
console.error(err);
return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.id), err, this.instantiationService, this.notificationService, this.openerService);
@@ -418,6 +425,13 @@ export class UpdateAction extends Action {
return this.notificationService.error(err);
}
// {{SQL CARBON EDIT}}
// Prompt the user that the current ADS version is not compatible with the extension,
// return here as in this scenario it doesn't make sense for the user to download manually.
if(err && err.code === INSTALL_ERROR_INCOMPATIBLE) {
return this.notificationService.error(err);
}
console.error(err);
return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.id), err, this.instantiationService, this.notificationService, this.openerService);

View File

@@ -13,9 +13,10 @@ import { isPromiseCanceledError } from 'vs/base/common/errors';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
// {{SQL CARBON EDIT}}
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest,
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionManagementServerService
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionManagementServerService, INSTALL_ERROR_INCOMPATIBLE
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, getLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -39,6 +40,11 @@ import * as resources from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
// {{SQL CARBON EDIT}}
import pkg from 'vs/platform/node/package';
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
import { ExtensionManagementError } from 'vs/platform/extensionManagement/node/extensionManagementService';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
}
@@ -703,6 +709,13 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
if (extensionIdentifier) {
this.checkAndEnableDisabledDependencies(extensionIdentifier);
}
}
// {{SQL CARBON EDIT}}
// This is the error handler when installing local VSIX file.
// Prompt the user about the error detail.
, (error) => {
this.notificationService.error(error);
return Promise.reject(error);
}));
}
@@ -721,6 +734,15 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
return Promise.reject(new Error('Missing gallery'));
}
// {{SQL CARBON EDIT}}
// This is the execution path for install/update extension from marketplace.
// Check both the vscode version and azure data studio version
// The check is added here because we want to fail fast instead of downloading the VSIX and then fail.
if (gallery.properties.engine && (!isEngineValid(gallery.properties.engine, product.vscodeVersion)
|| (gallery.properties.azDataEngine && !isEngineValid(gallery.properties.azDataEngine, pkg.version)))) {
return Promise.reject(new ExtensionManagementError(nls.localize('incompatible', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", extension.id, pkg.version, gallery.version), INSTALL_ERROR_INCOMPATIBLE));
}
return this.installWithProgress(
// {{SQL CARBON EDIT}}
() => {

View File

@@ -24,7 +24,7 @@ export interface IExtensionDescription {
readonly engines: {
vscode: string;
// {{SQL CARBON EDIT}}
sqlops?: string;
azdata?: string;
};
readonly main?: string;
readonly contributes?: { [point: string]: any; };

View File

@@ -89,8 +89,8 @@ class ExtensionManifestParser extends ExtensionManifestHandler {
manifest.uuid = manifest.__metadata.id;
}
// {{SQL CARBON EDIT}}
if (manifest.engines && !manifest.engines.sqlops) {
manifest.engines.sqlops = '*';
if (manifest.engines && !manifest.engines.azdata) {
manifest.engines.azdata = '*';
}
delete manifest.__metadata;
return manifest;