Skip to content

Commit

Permalink
Use preferences for node download template
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew committed Oct 25, 2023
1 parent f75d0e1 commit 98bc3e3
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { RemoteService } from './remote-service';
import { RemoteStatusService, RemoteStatusServicePath } from '../electron-common/remote-status-service';
import { ElectronFileDialogService } from '@theia/filesystem/lib/electron-browser/file-dialog/electron-file-dialog-service';
import { RemoteElectronFileDialogService } from './remote-electron-file-dialog-service';
import { bindRemotePreferences } from './remote-preferences';

export default new ContainerModule((bind, _, __, rebind) => {
bind(RemoteFrontendContribution).toSelf().inSingletonScope();
Expand All @@ -35,6 +36,8 @@ export default new ContainerModule((bind, _, __, rebind) => {
bind(RemoteSSHContribution).toSelf().inSingletonScope();
bind(RemoteRegistryContribution).toService(RemoteSSHContribution);

bindRemotePreferences(bind);

rebind(ElectronFileDialogService).to(RemoteElectronFileDialogService).inSingletonScope();

bind(RemoteService).toSelf().inSingletonScope();
Expand Down
62 changes: 62 additions & 0 deletions packages/remote/src/electron-browser/remote-preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// *****************************************************************************
// Copyright (C) 2023 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { interfaces } from '@theia/core/shared/inversify';
import {
PreferenceProxy,
PreferenceSchema,
PreferenceContribution
} from '@theia/core/lib/browser/preferences';
import { nls } from '@theia/core/lib/common/nls';
import { PreferenceProxyFactory } from '@theia/core/lib/browser/preferences/injectable-preference-proxy';

const nodeDownloadTemplateParts = [
nls.localize('theia/remote/nodeDownloadTemplateVersion', '`{version}` for the used node version'),
nls.localize('theia/remote/nodeDownloadTemplateOS', '`{os}` for the remote operating system. Either `win`, `linux` or `darwin`.'),
nls.localize('theia/remote/nodeDownloadTemplateArch', '`{arch}` for the remote system architecture.'),
nls.localize('theia/remote/nodeDownloadTemplateExt', '`{ext}` for the file extension. Either `zip`, `tar.xz` or `tar.xz`, depending on the operating system.')
];

export const RemotePreferenceSchema: PreferenceSchema = {
'type': 'object',
properties: {
'remote.nodeDownloadTemplate': {
type: 'string',
default: '',
markdownDescription: nls.localize(
'theia/remote/nodeDownloadTemplate',
'Controls the template used to download the node.js binaries for the remote backend. Points to the official node.js website by default. Uses multiple placeholders:'
) + '\n- ' + nodeDownloadTemplateParts.join('\n- ')
},
}
};

export interface RemoteConfiguration {
'remote.nodeDownloadTemplate': string;
}

export const RemotePreferenceContribution = Symbol('RemotePreferenceContribution');
export const RemotePreferences = Symbol('GettingStartedPreferences');
export type RemotePreferences = PreferenceProxy<RemoteConfiguration>;

export function bindRemotePreferences(bind: interfaces.Bind): void {
bind(RemotePreferences).toDynamicValue(ctx => {
const factory = ctx.container.get<PreferenceProxyFactory>(PreferenceProxyFactory);
return factory(RemotePreferenceSchema);
}).inSingletonScope();
bind(RemotePreferenceContribution).toConstantValue({ schema: RemotePreferenceSchema });
bind(PreferenceContribution).toService(RemotePreferenceContribution);
}
10 changes: 9 additions & 1 deletion packages/remote/src/electron-browser/remote-ssh-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Command, MessageService, nls, QuickInputService } from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
import { RemoteSSHConnectionProvider } from '../electron-common/remote-ssh-connection-provider';
import { AbstractRemoteRegistryContribution, RemoteRegistry } from './remote-registry-contribution';
import { RemotePreferences } from './remote-preferences';

export namespace RemoteSSHCommands {
export const CONNECT: Command = Command.toLocalizedCommand({
Expand All @@ -44,6 +45,9 @@ export class RemoteSSHContribution extends AbstractRemoteRegistryContribution {
@inject(MessageService)
protected readonly messageService: MessageService;

@inject(RemotePreferences)
protected readonly remotePreferences: RemotePreferences;

registerRemoteCommands(registry: RemoteRegistry): void {
registry.registerCommand(RemoteSSHCommands.CONNECT, {
execute: () => this.connect(true)
Expand Down Expand Up @@ -89,6 +93,10 @@ export class RemoteSSHContribution extends AbstractRemoteRegistryContribution {
}

async sendSSHConnect(host: string, user: string): Promise<string> {
return this.sshConnectionProvider.establishConnection(host, user);
return this.sshConnectionProvider.establishConnection({
host,
user,
nodeDownloadTemplate: this.remotePreferences['remote.nodeDownloadTemplate']
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ export const RemoteSSHConnectionProviderPath = '/remote/ssh';

export const RemoteSSHConnectionProvider = Symbol('RemoteSSHConnectionProvider');

export interface RemoteSSHConnectionOptions {
user?: string;
host?: string;
export interface RemoteSSHConnectionProviderOptions {
user: string;
host: string;
nodeDownloadTemplate?: string;
}

export interface RemoteSSHConnectionProvider {
establishConnection(host: string, user: string): Promise<string>;
isConnectionAlive(remoteId: string): Promise<boolean>;
establishConnection(options: RemoteSSHConnectionProviderOptions): Promise<string>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export class RemoteNodeSetupService {
protected readonly scriptService: RemoteSetupScriptService;

getNodeDirectoryName(platform: RemotePlatform): string {
return `node-v${REMOTE_NODE_VERSION}-${this.getPlatformName(platform)}-${platform.arch}`;
}

protected getPlatformName(platform: RemotePlatform): string {
let platformId: string;
if (platform.os === OS.Type.Windows) {
platformId = 'win';
Expand All @@ -49,8 +53,7 @@ export class RemoteNodeSetupService {
} else {
platformId = 'linux';
}
const dirName = `node-v${REMOTE_NODE_VERSION}-${platformId}-${platform.arch}`;
return dirName;
return platformId;
}

protected validatePlatform(platform: RemotePlatform): void {
Expand All @@ -67,7 +70,7 @@ export class RemoteNodeSetupService {
throw new Error(`Invalid architecture for ${platform.os}: '${platform.arch}'. Only ${supportedArch} are supported.`);
}

getNodeFileName(platform: RemotePlatform): string {
protected getNodeFileExtension(platform: RemotePlatform): string {
let fileExtension: string;
if (platform.os === OS.Type.Windows) {
fileExtension = 'zip';
Expand All @@ -76,16 +79,20 @@ export class RemoteNodeSetupService {
} else {
fileExtension = 'tar.xz';
}
return `${this.getNodeDirectoryName(platform)}.${fileExtension}`;
return fileExtension;
}

async downloadNode(platform: RemotePlatform): Promise<string> {
getNodeFileName(platform: RemotePlatform): string {
return `${this.getNodeDirectoryName(platform)}.${this.getNodeFileExtension(platform)}`;
}

async downloadNode(platform: RemotePlatform, downloadTemplate?: string): Promise<string> {
this.validatePlatform(platform);
const fileName = this.getNodeFileName(platform);
const tmpdir = os.tmpdir();
const localPath = path.join(tmpdir, fileName);
if (!await fs.pathExists(localPath)) {
const downloadPath = this.getDownloadPath(fileName);
const downloadPath = this.getDownloadPath(platform, downloadTemplate);
const downloadResult = await this.requestService.request({
url: downloadPath
});
Expand All @@ -94,18 +101,23 @@ export class RemoteNodeSetupService {
return localPath;
}

generateDownloadScript(platform: RemotePlatform, targetPath: string): string {
generateDownloadScript(platform: RemotePlatform, targetPath: string, downloadTemplate?: string): string {
this.validatePlatform(platform);
const fileName = this.getNodeFileName(platform);
const downloadPath = this.getDownloadPath(fileName);
const downloadPath = this.getDownloadPath(platform, downloadTemplate);
const zipPath = this.scriptService.joinPath(platform, targetPath, fileName);
const download = this.scriptService.downloadFile(platform, downloadPath, zipPath);
const unzip = this.scriptService.unzip(platform, zipPath, targetPath);
return this.scriptService.joinScript(platform, download, unzip);
}

protected getDownloadPath(fileName: string): string {
return `https://nodejs.org/dist/v${REMOTE_NODE_VERSION}/${fileName}`;
protected getDownloadPath(platform: RemotePlatform, downloadTemplate?: string): string {
const template = downloadTemplate || 'https://nodejs.org/dist/v{version}/node-v{version}-{os}-{arch}.{ext}';
const downloadPath = template
.replace(/{version}/g, REMOTE_NODE_VERSION)
.replace(/{os}/g, this.getPlatformName(platform))
.replace(/{arch}/g, platform.arch)
.replace(/{ext}/g, this.getNodeFileExtension(platform));
return downloadPath;
}

}
15 changes: 13 additions & 2 deletions packages/remote/src/electron-node/setup/remote-setup-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import { OS, THEIA_VERSION } from '@theia/core';
import { RemoteNodeSetupService } from './remote-node-setup-service';
import { RemoteSetupScriptService } from './remote-setup-script-service';

export interface RemoteSetupOptions {
connection: RemoteConnection;
report: RemoteStatusReport;
nodeDownloadTemplate?: string;
}

@injectable()
export class RemoteSetupService {

Expand All @@ -41,7 +47,12 @@ export class RemoteSetupService {
@inject(ApplicationPackage)
protected readonly applicationPackage: ApplicationPackage;

async setup(connection: RemoteConnection, report: RemoteStatusReport): Promise<void> {
async setup(options: RemoteSetupOptions): Promise<void> {
const {
connection,
report,
nodeDownloadTemplate
} = options;
report('Identifying remote system...');
// 1. Identify remote platform
const platform = await this.detectRemotePlatform(connection);
Expand All @@ -57,7 +68,7 @@ export class RemoteSetupService {
if (!nodeDirExists) {
report('Downloading and installing Node.js on remote...');
// Download the binaries locally and move it via SSH
const nodeArchive = await this.nodeSetupService.downloadNode(platform);
const nodeArchive = await this.nodeSetupService.downloadNode(platform, nodeDownloadTemplate);
const remoteNodeZip = this.scriptService.joinPath(platform, applicationDirectory, nodeFileName);
await connection.copy(nodeArchive, remoteNodeZip);
await this.unzipRemote(connection, platform, remoteNodeZip, applicationDirectory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import * as fs from '@theia/core/shared/fs-extra';
import SftpClient = require('ssh2-sftp-client');
import { Emitter, Event, MessageService, QuickInputService } from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
import { RemoteSSHConnectionProvider } from '../../electron-common/remote-ssh-connection-provider';
import { RemoteSSHConnectionProvider, RemoteSSHConnectionProviderOptions } from '../../electron-common/remote-ssh-connection-provider';
import { RemoteConnectionService } from '../remote-connection-service';
import { RemoteProxyServerProvider } from '../remote-proxy-server-provider';
import { RemoteConnection, RemoteExecOptions, RemoteExecResult, RemoteExecTester, RemoteStatusReport } from '../remote-types';
Expand Down Expand Up @@ -53,15 +53,19 @@ export class RemoteSSHConnectionProviderImpl implements RemoteSSHConnectionProvi
protected passwordRetryCount = 3;
protected passphraseRetryCount = 3;

async establishConnection(host: string, user: string): Promise<string> {
async establishConnection(options: RemoteSSHConnectionProviderOptions): Promise<string> {
const progress = await this.messageService.showProgress({
text: 'Remote SSH'
});
const report: RemoteStatusReport = message => progress.report({ message });
report('Connecting to remote system...');
try {
const remote = await this.establishSSHConnection(host, user);
await this.remoteSetup.setup(remote, report);
const remote = await this.establishSSHConnection(options.host, options.user);
await this.remoteSetup.setup({
connection: remote,
report,
nodeDownloadTemplate: options.nodeDownloadTemplate
});
const registration = this.remoteConnectionService.register(remote);
const server = await this.serverProvider.getProxyServer(socket => {
remote.forwardOut(socket);
Expand Down Expand Up @@ -230,11 +234,6 @@ export class RemoteSSHConnectionProviderImpl implements RemoteSSHConnectionProvi
callback(END_AUTH);
};
}

isConnectionAlive(remoteId: string): Promise<boolean> {
return Promise.resolve(Boolean(this.remoteConnectionService.getConnection(remoteId)));
}

}

export interface RemoteSSHConnectionOptions {
Expand Down
3 changes: 3 additions & 0 deletions packages/remote/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"references": [
{
"path": "../core"
},
{
"path": "../filesystem"
}
]
}
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8034,11 +8034,6 @@ [email protected]:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==

[email protected]:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==

nanoid@^3.3.6:
version "3.3.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
Expand Down

0 comments on commit 98bc3e3

Please sign in to comment.