Skip to content

Commit eb119c3

Browse files
test
Signed-off-by: Roman Nikitenko <rnikiten@redhat.com>
1 parent 89eb199 commit eb119c3

5 files changed

Lines changed: 146 additions & 14 deletions

File tree

code/src/vs/platform/extensionManagement/common/extensionManagement.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,10 @@ export type InstallOptions = {
575575
productVersion?: IProductVersion;
576576
keepExisting?: boolean;
577577
downloadExtensionsLocally?: boolean;
578+
/**
579+
* Indicates that this extension is from DEFAULT_EXTENSIONS and should bypass policy checks
580+
*/
581+
isDefault?: boolean;
578582
/**
579583
* Context passed through to InstallExtensionResult
580584
*/

code/src/vs/platform/extensionManagement/node/extensionManagementService.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
158158
this.logService.trace('ExtensionManagementService#install', vsix.toString());
159159
this.logService.info('+++++ ExtensionManagementService#install = ILC ', vsix.path);
160160

161+
const isDefaultExtension = options.isDefault === true;
162+
161163
const { location, cleanup } = await this.downloadVsix(vsix);
162164

163165
try {
@@ -168,19 +170,29 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
168170
}
169171

170172
// Block VSIX installations if the policy is configured and blockNonGalleryExtensions is enabled
171-
const blockNonGallery = this.configurationService.getValue<boolean>(BlockNonGalleryExtensionsConfigKey);
172-
this.logService.info('+++++ ExtensionManagementService +++ blockNonGallery ', blockNonGallery);
173-
const hasPolicy = this.configurationService.inspect(AllowedExtensionsConfigKey).policy !== undefined;
174-
if (hasPolicy && blockNonGallery) {
175-
this.logService.info('+++++ ExtensionManagementService +++ hasPolicy ', hasPolicy);
176-
throw new Error(nls.localize('vsix not allowed', "VSIX files cannot be installed because only extensions from the gallery are allowed. Please install this extension from the Extensions marketplace."));
173+
// Skip this check for default extensions (from DEFAULT_EXTENSIONS)
174+
if (!isDefaultExtension) {
175+
const blockNonGallery = this.configurationService.getValue<boolean>(BlockNonGalleryExtensionsConfigKey);
176+
this.logService.info('+++++ ExtensionManagementService +++ blockNonGallery ', blockNonGallery);
177+
const hasPolicy = this.configurationService.inspect(AllowedExtensionsConfigKey).policy !== undefined;
178+
if (hasPolicy && blockNonGallery) {
179+
this.logService.info('+++++ ExtensionManagementService +++ hasPolicy ', hasPolicy);
180+
throw new Error(nls.localize('vsix not allowed', "VSIX files cannot be installed because only extensions from the gallery are allowed. Please install this extension from the Extensions marketplace."));
181+
} else {
182+
this.logService.info('+++++ ExtensionManagementService +++ NOT hasPolicy ', hasPolicy);
183+
}
177184
} else {
178-
this.logService.info('+++++ ExtensionManagementService +++ NOT hasPolicy ', hasPolicy);
185+
this.logService.info('!!!!!!!! ExtensionManagementService#install - skipping blockNonGallery check for default extension');
179186
}
180187

181-
const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extensionId, version: manifest.version, publisherDisplayName: undefined });
182-
if (allowedToInstall !== true) {
183-
throw new Error(nls.localize('notAllowed', "This extension cannot be installed because {0}", allowedToInstall.value));
188+
// Skip allowedExtensionsService check for default extensions (from DEFAULT_EXTENSIONS)
189+
if (!isDefaultExtension) {
190+
const allowedToInstall = this.allowedExtensionsService.isAllowed({ id: extensionId, version: manifest.version, publisherDisplayName: undefined });
191+
if (allowedToInstall !== true) {
192+
throw new Error(nls.localize('notAllowed', "This extension cannot be installed because {0}", allowedToInstall.value));
193+
}
194+
} else {
195+
this.logService.info('!!!!!!!! ExtensionManagementService#install - skipping allowedExtensionsService check for default extension');
184196
}
185197

186198
const results = await this.installExtensions([{ manifest, extension: location, options }]);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**********************************************************************
2+
* Copyright (c) 2025 Red Hat, Inc.
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
***********************************************************************/
10+
/* eslint-disable header/header */
11+
12+
import * as path from '../../../base/common/path.js';
13+
import { VSBuffer } from '../../../base/common/buffer.js';
14+
import { Disposable } from '../../../base/common/lifecycle.js';
15+
import { URI } from '../../../base/common/uri.js';
16+
import { IFileService } from '../../../platform/files/common/files.js';
17+
import { INativeServerExtensionManagementService } from '../../../platform/extensionManagement/node/extensionManagementService.js';
18+
import { ILogService } from '../../../platform/log/common/log.js';
19+
import { IUserDataProfilesService } from '../../../platform/userDataProfile/common/userDataProfile.js';
20+
21+
export class DefaultExtensionsInstaller extends Disposable {
22+
23+
constructor(
24+
private readonly extensionManagementService: INativeServerExtensionManagementService,
25+
private readonly logService: ILogService,
26+
private readonly fileService: IFileService,
27+
private readonly userDataProfilesService: IUserDataProfilesService,
28+
) {
29+
super();
30+
this.initialize().catch(error => {
31+
this.logService.error('Failed to initialize default extensions installer', error);
32+
});
33+
}
34+
35+
private async initialize(): Promise<void> {
36+
const defaultExtensionsEnv = typeof process !== 'undefined' && process.env ? process.env['DEFAULT_EXTENSIONS'] : undefined;
37+
if (!defaultExtensionsEnv) {
38+
this.logService.debug('DefaultExtensionsInstaller: DEFAULT_EXTENSIONS not set, skipping installation');
39+
return;
40+
}
41+
42+
const extensionPaths = defaultExtensionsEnv.split(';').filter(p => p.trim());
43+
if (extensionPaths.length === 0) {
44+
this.logService.debug('DefaultExtensionsInstaller: No extensions to install');
45+
return;
46+
}
47+
48+
this.logService.info(`DefaultExtensionsInstaller: Found ${extensionPaths.length} default extension(s) in DEFAULT_EXTENSIONS`);
49+
50+
// Track installed extensions in a file to avoid reinstalling
51+
const storageFile = this.getStorageFile();
52+
let installedPaths: string[] = [];
53+
try {
54+
const content = await this.fileService.readFile(storageFile);
55+
installedPaths = JSON.parse(content.value.toString());
56+
} catch (e) {
57+
// File doesn't exist or is invalid, start fresh
58+
installedPaths = [];
59+
}
60+
61+
const pathsToInstall = extensionPaths.filter(p => !installedPaths.includes(p.trim()));
62+
if (pathsToInstall.length === 0) {
63+
this.logService.debug('DefaultExtensionsInstaller: All default extensions already installed');
64+
return;
65+
}
66+
67+
this.logService.info(`DefaultExtensionsInstaller: Installing ${pathsToInstall.length} new default extension(s)`);
68+
await this.installExtensions(pathsToInstall, storageFile, installedPaths);
69+
}
70+
71+
private async installExtensions(
72+
pathsToInstall: string[],
73+
storageFile: URI,
74+
installedPaths: string[]
75+
): Promise<void> {
76+
const successfullyInstalled: string[] = [];
77+
78+
for (const extensionPath of pathsToInstall) {
79+
const trimmedPath = extensionPath.trim();
80+
if (!trimmedPath) {
81+
continue;
82+
}
83+
84+
try {
85+
const vsixUri = URI.file(trimmedPath);
86+
this.logService.info(`DefaultExtensionsInstaller: Installing extension from ${trimmedPath}`);
87+
88+
await this.extensionManagementService.install(vsixUri, {
89+
donotIncludePackAndDependencies: true,
90+
isDefault: true // Mark as default extension to bypass policy checks
91+
});
92+
successfullyInstalled.push(trimmedPath);
93+
this.logService.info(`DefaultExtensionsInstaller: Successfully installed extension from ${trimmedPath}`);
94+
} catch (error) {
95+
this.logService.error(`DefaultExtensionsInstaller: Failed to install extension from ${trimmedPath}`, error);
96+
// Continue with other extensions even if one fails
97+
}
98+
}
99+
100+
// Update storage with successfully installed extensions
101+
if (successfullyInstalled.length > 0) {
102+
const updatedInstalled = [...installedPaths, ...successfullyInstalled];
103+
await this.fileService.writeFile(storageFile, VSBuffer.fromString(JSON.stringify(updatedInstalled, null, 2)));
104+
this.logService.info(`DefaultExtensionsInstaller: Updated storage with ${successfullyInstalled.length} successfully installed extension(s)`);
105+
}
106+
}
107+
108+
private getStorageFile(): URI {
109+
// Store in the same directory as the extensions profile manifest
110+
return this.userDataProfilesService.defaultProfile.extensionsResource.with({
111+
path: path.join(path.dirname(this.userDataProfilesService.defaultProfile.extensionsResource.path), '.default-extensions-installed.json')
112+
});
113+
}
114+
}
115+

code/src/vs/server/node/serverServices.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import { IServerTelemetryService, ServerNullTelemetryService, ServerTelemetrySer
5757
import { RemoteTerminalChannel } from './remoteTerminalChannel.js';
5858
import { createURITransformer } from '../../workbench/api/node/uriTransformer.js';
5959
import { ServerConnectionToken } from './serverConnectionToken.js';
60+
import { DefaultExtensionsInstaller } from './che/defaultExtensionsInstaller.js';
6061
import { ServerEnvironmentService, ServerParsedArgs } from './serverEnvironmentService.js';
6162
import { REMOTE_TERMINAL_CHANNEL_NAME } from '../../workbench/contrib/terminal/common/remote/remoteTerminalChannel.js';
6263
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from '../../workbench/services/remote/common/remoteFileSystemProviderClient.js';
@@ -275,6 +276,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
275276
// clean up extensions folder
276277
remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp());
277278

279+
// Install default extensions from DEFAULT_EXTENSIONS environment variable
280+
disposables.add(new DefaultExtensionsInstaller(extensionManagementService, logService, fileService, userDataProfilesService));
281+
278282
disposables.add(new ErrorTelemetry(accessor.get(ITelemetryService)));
279283

280284
return {

code/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,13 +2024,10 @@ class DefaultExtensionsInitializer implements IWorkbenchContribution {
20242024
) {
20252025
// Initialize AllowedExtensionsService with DEFAULT_EXTENSIONS from environment service
20262026
// This allows the service to validate extensions against the environment variable
2027+
// Note: Actual installation happens on the server side where file system access is available
20272028
const defaultExtensions = environmentService.defaultExtensions;
2028-
console.log('!!!!!!!! DefaultExtensionsInitializer - defaultExtensions from env service:', defaultExtensions);
20292029
if (defaultExtensions) {
20302030
(allowedExtensionsService as any).setDefaultExtensionsEnv(defaultExtensions);
2031-
console.log('!!!!!!!! DefaultExtensionsInitializer - set on allowedExtensionsService');
2032-
} else {
2033-
console.log('!!!!!!!! DefaultExtensionsInitializer - defaultExtensions is undefined');
20342031
}
20352032
}
20362033
}

0 commit comments

Comments
 (0)