Add `forwardings` to FileSystemConfig and auto-forward on connection creation

feature/forwarding
Kelvin Schoofs 3 years ago
parent cf941c73c6
commit c217261eab

@ -5,7 +5,8 @@ import * as vscode from 'vscode';
import { configMatches, getFlagBoolean, loadConfigs } from './config';
import type { EnvironmentVariable, FileSystemConfig } from './fileSystemConfig';
import { Logging, LOGGING_NO_STACKTRACE } from './logging';
import { ActivePortForwarding } from './portForwarding';
import type { Manager } from './manager';
import { ActivePortForwarding, addForwarding, formatPortForwarding, parsePortForwarding } from './portForwarding';
import type { SSHPseudoTerminal } from './pseudoTerminal';
import type { SSHFileSystem } from './sshFileSystem';
import { mergeEnvironment, toPromise } from './utils';
@ -66,6 +67,7 @@ export class ConnectionManager {
public readonly onConnectionUpdated = this.onConnectionUpdatedEmitter.event;
/** Fired when a pending connection gets added/removed */
public readonly onPendingChanged = this.onPendingChangedEmitter.event;
public constructor(public readonly manager: Manager) { }
public getActiveConnection(name: string, config?: FileSystemConfig): Connection | undefined {
const con = config && this.connections.find(con => configMatches(con.config, config));
return con || (config ? undefined : this.connections.find(con => con.config.name === name));
@ -206,6 +208,27 @@ export class ConnectionManager {
vscode.window.showErrorMessage(`Reconnect failed: ${e.message || e}`);
});
});
// Setup initial port forwardings
setImmediate(async () => {
const forwards = (actualConfig.forwardings || []).map(parsePortForwarding);
const badForwards = forwards.reduce((tot, f) => f ? tot : tot + 1, 0);
if (badForwards) vscode.window.showWarningMessage(`Could not parse ${badForwards} of ${forwards.length} port forwarding from the config, ignoring them`);
let failed = 0;
console.log(forwards);
for (const forward of forwards) {
logging.debug(`forward: ${forward}`);
if (!forward) continue;
logging.info(`Adding forwarding ${formatPortForwarding(forward)}`);
try {
await addForwarding(this.manager, con, forward);
} catch (e) {
logging.error(`Error during forwarding ${formatPortForwarding(forward)}:`, LOGGING_NO_STACKTRACE);
logging.error(e);
failed++;
}
}
if (failed) vscode.window.showWarningMessage(`Failed ${failed} of ${forwards.length - badForwards} port forwardings from the config`);
});
this.connections.push(con);
this.onConnectionAddedEmitter.fire(con);
return con;

@ -10,8 +10,8 @@ export type ConfigLocation = number | string;
/** Might support conditional stuff later, although ssh2/OpenSSH might not support that natively */
export interface EnvironmentVariable {
key: string;
value: string;
key: string;
value: string;
}
export function formatConfigLocation(location?: ConfigLocation): string {
@ -114,6 +114,8 @@ export interface FileSystemConfig extends ConnectConfig {
instantConnection?: boolean;
/** List of special flags to enable/disable certain fixes/features. Flags are usually used for issues or beta testing. Flags can disappear/change anytime! */
flags?: string[];
/** List of port forwardings to (attempt to) establish when the connection gets created */
forwardings?: string[];
/** Internal property saying where this config comes from. Undefined if this config is merged or something */
_location?: ConfigLocation;
/** Internal property keeping track of where this config comes from (including merges) */

@ -30,7 +30,7 @@ interface TerminalLinkUri extends vscode.TerminalLink {
export class Manager implements vscode.TaskProvider, vscode.TerminalLinkProvider<TerminalLinkUri> {
protected fileSystems: SSHFileSystem[] = [];
protected creatingFileSystems: { [name: string]: Promise<SSHFileSystem> } = {};
public readonly connectionManager = new ConnectionManager();
public readonly connectionManager = new ConnectionManager(this);
constructor(public readonly context: vscode.ExtensionContext) {
// In a multi-workspace environment, when the non-main folder gets removed,
// it might be one of ours, which we should then disconnect if it's

@ -32,6 +32,40 @@ export interface PortForwardingLocalRemote {
export type PortForwarding = PortForwardingDynamic | PortForwardingLocalRemote;
// https://regexr.com/61quq
const PORT_FORWARD_REGEX = /^^(?<type>[a-z]+)\s*?(?:\s+(?:(?<localAddress>[^\s:]+):))?(?<localPort>\d+)(?:\s+(?:(?<remoteAddress>[^\s:]+):)?(?<remotePort>\d+))?$/i;
const PORT_FORWARD_TYPES = ['remote', 'local', 'dynamic'];
export function parsePortForwarding(input: string): PortForwarding | undefined {
try {
const match = input.match(PORT_FORWARD_REGEX);
if (!match) throw new Error(`Could not infer PortForwarding from '${input}'`);
let type = match.groups?.type.toLowerCase();
if (!type) throw new Error(`Could not infer PortForwarding from '${input}'`);
if (type.endsWith('forward')) type = type.substring(0, type.length - 7);
if (type.length === 1) type = PORT_FORWARD_TYPES.find(t => t[0] === type);
if (!type || !PORT_FORWARD_TYPES.includes(type))
throw new Error(`Could not recognize PortForwarding type '${match.groups!.type}'`);
const { localAddress, localPort, remoteAddress, remotePort } = match.groups as Partial<Record<string, string>>;
let pf: PortForwarding;
if (type === 'remote' && !remoteAddress && !remotePort) {
pf = { type, remoteAddress: localAddress, remotePort: parseInt(localPort!) };
} else if (type === 'local' || type === 'remote') {
pf = {
type,
localAddress, localPort: parseInt(localPort!),
remoteAddress, remotePort: remotePort ? parseInt(remotePort) : undefined,
};
} else {
pf = { type: 'dynamic', address: localAddress, port: parseInt(localPort!) };
}
validatePortForwarding(pf);
return pf;
} catch (e) {
Logging.error(`Parsing port forwarding '${input}' failed:\n${e.message || e}`, LOGGING_NO_STACKTRACE);
return undefined;
}
}
const formatAddrPortPath = (addr?: string, port?: number): string => {
if (port === undefined) return addr || 'N/A';
return `${addr || '*'}:${port}`;

Loading…
Cancel
Save