Skip to content

Commit

Permalink
Always use public API (#104)
Browse files Browse the repository at this point in the history
* Always use public API

* 💄
  • Loading branch information
mustard-mh authored Jun 14, 2024
1 parent 4906186 commit 22c39f2
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 89 deletions.
5 changes: 0 additions & 5 deletions src/experiments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const EXPERIMENTAL_SETTINGS: string[] = [

export interface IExperimentsService {
getUseLocalSSHProxy(): Promise<boolean>;
getUsePublicAPI(gitpodHost: string): Promise<boolean>;
}

export class ExperimentalSettings extends Disposable implements IExperimentsService {
Expand Down Expand Up @@ -147,10 +146,6 @@ export class ExperimentalSettings extends Disposable implements IExperimentsServ
async getUseLocalSSHProxy(): Promise<boolean> {
return (await this.getRaw<boolean>('gitpod_desktop_use_local_ssh_proxy', { 'platform': os.platform() })) ?? false;
}

async getUsePublicAPI(gitpodHost: string): Promise<boolean> {
return (await this.getRaw<boolean>('gitpod_experimental_publicApi', { gitpodHost })) ?? false;
}
}

export function isUserOverrideSetting(key: string): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export async function activate(context: vscode.ExtensionContext) {
// Because auth provider implementation is in the same extension, we need to wait for it to activate first
firstLoadPromise.then(async () => {
if (remoteConnectionInfo) {
remoteSession = new RemoteSession(remoteConnectionInfo.connectionInfo, context, remoteService, hostService, sessionService, experiments, logger!, telemetryService!, notificationService);
remoteSession = new RemoteSession(remoteConnectionInfo.connectionInfo, context, remoteService, hostService, sessionService, logger!, telemetryService!, notificationService);
await remoteSession.initialize();
} else if (sessionService.isSignedIn()) {
remoteService.checkForStoppedWorkspaces(async wsInfo => {
Expand Down
59 changes: 20 additions & 39 deletions src/remoteConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
*--------------------------------------------------------------------------------------------*/

import { Workspace, WorkspaceInstanceStatus_Phase } from '@gitpod/public-api/lib/gitpod/experimental/v1/workspaces_pb';
import { UserSSHPublicKeyValue, WorkspaceInfo } from '@gitpod/gitpod-protocol';
import * as crypto from 'crypto';
import { utils as sshUtils } from 'ssh2';
import { ParsedKey } from 'ssh2-streams';
import * as vscode from 'vscode';
import { Disposable } from './common/dispose';
import { withServerApi } from './internalApi';
import { ITelemetryService, UserFlowTelemetryProperties } from './common/telemetry';
import { addHostToHostFile, checkNewHostInHostkeys } from './ssh/hostfile';
import { ScopeFeature } from './featureSupport';
Expand All @@ -33,8 +31,6 @@ export class RemoteConnector extends Disposable {

public static AUTH_COMPLETE_PATH = '/auth-complete';

private usePublicApi: boolean = false;

constructor(
private readonly context: vscode.ExtensionContext,
private readonly sessionService: ISessionService,
Expand All @@ -55,27 +51,21 @@ export class RemoteConnector extends Disposable {
private async getWorkspaceSSHDestination({ workspaceId, gitpodHost, debugWorkspace }: SSHConnectionParams): Promise<{ destination: SSHDestination; password?: string }> {
const sshKeysSupported = this.sessionService.getScopes().includes(ScopeFeature.SSHPublicKeys);

const [workspaceInfo, ownerToken, registeredSSHKeys] = await withServerApi(this.sessionService.getGitpodToken(), getServiceURL(gitpodHost), service => Promise.all([
this.usePublicApi ? this.sessionService.getAPI().getWorkspace(workspaceId) : service.server.getWorkspace(workspaceId),
this.usePublicApi ? this.sessionService.getAPI().getOwnerToken(workspaceId) : service.server.getOwnerToken(workspaceId),
sshKeysSupported ? (this.usePublicApi ? this.sessionService.getAPI().getSSHKeys() : service.server.getSSHPublicKeys()) : undefined
]), this.logService);
const [workspaceInfo, ownerToken, registeredSSHKeys] = await Promise.all([
this.sessionService.getAPI().getWorkspace(workspaceId),
this.sessionService.getAPI().getOwnerToken(workspaceId),
sshKeysSupported ? (this.sessionService.getAPI().getSSHKeys()) : undefined
]);

const isNotRunning = this.usePublicApi
? !((workspaceInfo as Workspace)?.status?.instance) || (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.STOPPING || (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.STOPPED
: !((workspaceInfo as WorkspaceInfo).latestInstance) || (workspaceInfo as WorkspaceInfo).latestInstance?.status?.phase === 'stopping' || (workspaceInfo as WorkspaceInfo).latestInstance?.status?.phase === 'stopped';
const isNotRunning = !((workspaceInfo as Workspace)?.status?.instance) || (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.STOPPING || (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.STOPPED;
if (isNotRunning) {
throw new NoRunningInstanceError(
workspaceId,
this.usePublicApi
? (workspaceInfo as Workspace)?.status?.instance?.status?.phase ? WorkspaceInstanceStatus_Phase[(workspaceInfo as Workspace)?.status?.instance?.status?.phase!] : undefined
: (workspaceInfo as WorkspaceInfo).latestInstance?.status?.phase
(workspaceInfo as Workspace)?.status?.instance?.status?.phase ? WorkspaceInstanceStatus_Phase[(workspaceInfo as Workspace)?.status?.instance?.status?.phase!] : undefined
);
}

const workspaceUrl = this.usePublicApi
? new URL((workspaceInfo as Workspace).status!.instance!.status!.url)
: new URL((workspaceInfo as WorkspaceInfo).latestInstance!.ideUrl);
const workspaceUrl = new URL((workspaceInfo as Workspace).status!.instance!.status!.url);

const sshHostKeyEndPoint = `https://${workspaceUrl.host}/_ssh/host_keys`;
const sshHostKeyResponse = await fetch(sshHostKeyEndPoint);
Expand Down Expand Up @@ -122,18 +112,16 @@ export class RemoteConnector extends Disposable {
let identityKeys = await gatherIdentityFiles(identityFiles, getAgentSock(hostConfiguration), false, this.logService);

if (registeredSSHKeys) {
const registeredKeys = this.usePublicApi
? (registeredSSHKeys as SSHKey[]).map(k => {
const parsedResult = sshUtils.parseKey(k.key);
if (parsedResult instanceof Error || !parsedResult) {
this.logService.error(`Error while parsing SSH public key ${k.name}:`, parsedResult);
return { name: k.name, fingerprint: '' };
}
const registeredKeys = (registeredSSHKeys as SSHKey[]).map(k => {
const parsedResult = sshUtils.parseKey(k.key);
if (parsedResult instanceof Error || !parsedResult) {
this.logService.error(`Error while parsing SSH public key ${k.name}:`, parsedResult);
return { name: k.name, fingerprint: '' };
}

const parsedKey = parsedResult as ParsedKey;
return { name: k.name, fingerprint: crypto.createHash('sha256').update(parsedKey.getPublicSSH()).digest('base64') };
})
: (registeredSSHKeys as UserSSHPublicKeyValue[]).map(k => ({ name: k.name, fingerprint: k.fingerprint }));
const parsedKey = parsedResult as ParsedKey;
return { name: k.name, fingerprint: crypto.createHash('sha256').update(parsedKey.getPublicSSH()).digest('base64') };
});
this.logService.trace(`Registered public keys in Gitpod account:`, registeredKeys.length ? registeredKeys.map(k => `${k.name} SHA256:${k.fingerprint}`).join('\n') : 'None');

identityKeys = identityKeys.filter(k => !!registeredKeys.find(regKey => regKey.fingerprint === k.fingerprint));
Expand All @@ -151,18 +139,14 @@ export class RemoteConnector extends Disposable {
}

private async getLocalSSHWorkspaceSSHDestination({ workspaceId, gitpodHost, debugWorkspace }: SSHConnectionParams): Promise<{ destination: SSHDestination; password?: string }> {
const workspaceInfo = await withServerApi(this.sessionService.getGitpodToken(), getServiceURL(gitpodHost), async service => this.usePublicApi ? this.sessionService.getAPI().getWorkspace(workspaceId) : service.server.getWorkspace(workspaceId), this.logService);
const workspaceInfo = await this.sessionService.getAPI().getWorkspace(workspaceId);

const isNotRunning = this.usePublicApi
? !((workspaceInfo as Workspace)?.status?.instance) || (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.STOPPING || (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.STOPPED
: !((workspaceInfo as WorkspaceInfo).latestInstance) || (workspaceInfo as WorkspaceInfo).latestInstance?.status?.phase === 'stopping' || (workspaceInfo as WorkspaceInfo).latestInstance?.status?.phase === 'stopped';
const isNotRunning = !((workspaceInfo as Workspace)?.status?.instance) || (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.STOPPING || (workspaceInfo as Workspace)?.status?.instance?.status?.phase === WorkspaceInstanceStatus_Phase.STOPPED;

if (isNotRunning) {
throw new NoRunningInstanceError(
workspaceId,
this.usePublicApi
? (workspaceInfo as Workspace)?.status?.instance?.status?.phase ? WorkspaceInstanceStatus_Phase[(workspaceInfo as Workspace)?.status?.instance?.status?.phase!] : undefined
: (workspaceInfo as WorkspaceInfo).latestInstance?.status?.phase
(workspaceInfo as Workspace)?.status?.instance?.status?.phase ? WorkspaceInstanceStatus_Phase[(workspaceInfo as Workspace)?.status?.instance?.status?.phase!] : undefined,
);
}

Expand Down Expand Up @@ -271,9 +255,6 @@ export class RemoteConnector extends Disposable {
location: vscode.ProgressLocation.Notification
},
async () => {
this.usePublicApi = await this.experiments.getUsePublicAPI(params.gitpodHost);
this.logService.info(`Going to use ${this.usePublicApi ? 'public' : 'server'} API`);

const openSSHVersion = await getOpenSSHVersion();

// Always try to run a local ssh connection collect success metrics
Expand Down
65 changes: 21 additions & 44 deletions src/remoteSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import { NoRunningInstanceError, SSHConnectionParams, SSH_DEST_KEY, getGitpodRem
import { Disposable } from './common/dispose';
import { HeartbeatManager } from './heartbeat';
import { WorkspaceState } from './workspaceState';
import { IExperimentsService } from './experiments';
import { ITelemetryService, UserFlowTelemetryProperties } from './common/telemetry';
import { INotificationService } from './services/notificationService';
import { withServerApi } from './internalApi';
import { ISessionService } from './services/sessionService';
import { IHostService } from './services/hostService';
import { ILogService } from './services/logService';
Expand All @@ -20,8 +18,6 @@ import { IRemoteService } from './services/remoteService';

export class RemoteSession extends Disposable {

private usePublicApi: boolean = false;

private heartbeatManager: HeartbeatManager | undefined;
private workspaceState: WorkspaceState | undefined;
private extensionServiceServer: ExtensionServiceServer | undefined;
Expand All @@ -32,7 +28,6 @@ export class RemoteSession extends Disposable {
private readonly remoteService: IRemoteService,
private readonly hostService: IHostService,
private readonly sessionService: ISessionService,
private readonly experiments: IExperimentsService,
private readonly logService: ILogService,
private readonly telemetryService: ITelemetryService,
private readonly notificationService: INotificationService
Expand Down Expand Up @@ -63,45 +58,27 @@ export class RemoteSession extends Disposable {
try {
this.remoteService.startLocalSSHServiceServer().catch(() => {/* ignore */ });

this.usePublicApi = await this.experiments.getUsePublicAPI(this.connectionInfo.gitpodHost);
this.logService.info(`Going to use ${this.usePublicApi ? 'public' : 'server'} API`);

if (this.usePublicApi) {
this.workspaceState = new WorkspaceState(this.connectionInfo.workspaceId, this.sessionService, this.logService);
this.workspaceState.initialize()
.then(() => {
if (!this.workspaceState!.instanceId || !this.workspaceState!.isWorkspaceRunning) {
vscode.commands.executeCommand('workbench.action.remote.close');
return;
}
const instanceId = this.workspaceState!.instanceId;
if (instanceId !== this.connectionInfo.instanceId) {
this.logService.info(`Updating workspace ${this.connectionInfo.workspaceId} latest instance id ${this.connectionInfo.instanceId} => ${instanceId}`);
this.connectionInfo.instanceId = instanceId;
}

const { sshDestStr } = getGitpodRemoteWindowConnectionInfo(this.context)!;
this.context.globalState.update(`${SSH_DEST_KEY}${sshDestStr}`, { ...this.connectionInfo } as SSHConnectionParams);
});

this._register(this.workspaceState.onWorkspaceWillStop(async () => {
await this.remoteService.saveRestartInfo();
vscode.commands.executeCommand('workbench.action.remote.close');
}));
} else {
const workspaceInfo = await withServerApi(this.sessionService.getGitpodToken(), this.connectionInfo.gitpodHost, service => service.server.getWorkspace(this.connectionInfo.workspaceId), this.logService);
if (!workspaceInfo.latestInstance || workspaceInfo.latestInstance?.status?.phase === 'stopping' || workspaceInfo.latestInstance?.status?.phase === 'stopped') {
throw new NoRunningInstanceError(this.connectionInfo.workspaceId, workspaceInfo.latestInstance?.status?.phase);
}
const instanceId = workspaceInfo.latestInstance.id;
if (instanceId !== this.connectionInfo.instanceId) {
this.logService.info(`Updating workspace ${this.connectionInfo.workspaceId} latest instance id ${this.connectionInfo.instanceId} => ${instanceId}`);
this.connectionInfo.instanceId = instanceId;
}

const { sshDestStr } = getGitpodRemoteWindowConnectionInfo(this.context)!;
this.context.globalState.update(`${SSH_DEST_KEY}${sshDestStr}`, { ...this.connectionInfo } as SSHConnectionParams);
}
this.workspaceState = new WorkspaceState(this.connectionInfo.workspaceId, this.sessionService, this.logService);
this.workspaceState.initialize()
.then(() => {
if (!this.workspaceState!.instanceId || !this.workspaceState!.isWorkspaceRunning) {
vscode.commands.executeCommand('workbench.action.remote.close');
return;
}
const instanceId = this.workspaceState!.instanceId;
if (instanceId !== this.connectionInfo.instanceId) {
this.logService.info(`Updating workspace ${this.connectionInfo.workspaceId} latest instance id ${this.connectionInfo.instanceId} => ${instanceId}`);
this.connectionInfo.instanceId = instanceId;
}

const { sshDestStr } = getGitpodRemoteWindowConnectionInfo(this.context)!;
this.context.globalState.update(`${SSH_DEST_KEY}${sshDestStr}`, { ...this.connectionInfo } as SSHConnectionParams);
});

this._register(this.workspaceState.onWorkspaceWillStop(async () => {
await this.remoteService.saveRestartInfo();
vscode.commands.executeCommand('workbench.action.remote.close');
}));

this.heartbeatManager = new HeartbeatManager(this.connectionInfo, this.workspaceState, this.sessionService, this.logService, this.telemetryService);

Expand Down

0 comments on commit 22c39f2

Please sign in to comment.