Skip to content

server changes to enable new JB integration #7711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions chart/templates/server-ide-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ options:

defaultIde: "code"
defaultDesktopIde: "code-desktop"

clients:
vscode:
defaultDesktopIDE: "code-desktop"
desktopIDEs: ["code-desktop"]
installationSteps: [
"If you don't see an open dialog by the browser, make sure you have <a target='_blank' class='gp-link' href='https://code.visualstudio.com/download'>VS Code</a> installed on your machine, and then click <b>${OPEN_LINK_LABEL}</b> below.",
]
vscode-insiders:
defaultDesktopIDE: "code-desktop-insiders"
desktopIDEs: ["code-desktop-insiders"]
installationSteps: [
"If you don't see an open dialog by the browser, make sure you have <a target='_blank' class='gp-link' href='https://code.visualstudio.com/insiders'>VS Code Insiders</a> installed on your machine, and then click <b>${OPEN_LINK_LABEL}</b> below.",
]
jetbrains-gateway:
defaultDesktopIDE: "intellij"
desktopIDEs: ["intellij", "goland", "pycharm", "phpstorm"]
installationSteps: [
"If you don't see an open dialog by the browser, make sure you have <a target='_blank' class='gp-link' href='https://www.jetbrains.com/remote-development/gateway'>JetBrains Gateway</a> with <a target='_blank' class='gp-link' href='https://plugins.jetbrains.com/plugin/download?rel=true&updateId=154074'>Gitpod Plugin</a> installed on your machine, and then click <b>${OPEN_LINK_LABEL}</b> below.",
]
{{ end }}

{{- if $comp.serverIdeConfigDeploy.enabled }}
Expand Down
2 changes: 1 addition & 1 deletion components/dashboard/src/start/StartPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function getPhaseTitle(phase?: StartPhase, error?: StartWorkspaceError) {
case StartPhase.Running:
return "Starting";
case StartPhase.IdeReady:
return "Your Workspace is Ready!";
return "Running";
case StartPhase.Stopping:
return "Stopping";
case StartPhase.Stopped:
Expand Down
40 changes: 33 additions & 7 deletions components/dashboard/src/start/StartWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { ContextURL, DisposableCollection, WithPrebuild, Workspace, WorkspaceImageBuild, WorkspaceInstance } from "@gitpod/gitpod-protocol";
import { IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol";
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import EventEmitter from "events";
import React, { Suspense, useEffect } from "react";
Expand Down Expand Up @@ -32,7 +33,9 @@ export interface StartWorkspaceState {
desktopIde?: {
link: string
label: string
clientID?: string
}
ideOptions?: IDEOptions
}

export default class StartWorkspace extends React.Component<StartWorkspaceProps, StartWorkspaceState> {
Expand All @@ -54,7 +57,8 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
}
if (event.data.state.desktopIdeLink) {
const label = event.data.state.desktopIdeLabel || "Open Desktop IDE";
this.setState({ desktopIde: { link: event.data.state.desktopIdeLink, label } });
const clientID = event.data.state.desktopIdeClientID;
this.setState({ desktopIde: { link: event.data.state.desktopIdeLink, label, clientID } });
}
}
}
Expand All @@ -72,6 +76,7 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
}

this.startWorkspace();
getGitpodService().server.getIDEOptions().then(ideOptions => this.setState({ ideOptions }))
}

componentWillUnmount() {
Expand Down Expand Up @@ -238,6 +243,7 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
let phase = StartPhase.Preparing;
let title = undefined;
let statusMessage = !!error ? undefined : <p className="text-base text-gray-400">Preparing workspace …</p>;
const contextURL = this.state.workspace?.context.normalizedContextURL || ContextURL.parseToURL(this.state.workspace?.contextURL)?.toString();

switch (this.state?.workspaceInstance?.status.phase) {
// unknown indicates an issue within the system in that it cannot determine the actual phase of
Expand Down Expand Up @@ -281,17 +287,26 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
}
if (!this.state.desktopIde) {
phase = StartPhase.Running;
statusMessage = <p className="text-base text-gray-400">Opening IDE …</p>;
statusMessage = <p className="text-base text-gray-400">Opening Workspace …</p>;
} else {
phase = StartPhase.IdeReady;
const openLink = this.state.desktopIde.link;
const openLinkLabel = this.state.desktopIde.label;
const clientID = this.state.desktopIde.clientID
const client = clientID ? this.state.ideOptions?.clients?.[clientID] : undefined;
const installationSteps = client?.installationSteps?.length && <div className="flex flex-col text-center m-auto text-sm w-72 text-gray-400">
{client.installationSteps.map(step => <div dangerouslySetInnerHTML={{__html: step.replaceAll('${OPEN_LINK_LABEL}', openLinkLabel)}} />)}
</div>
statusMessage = <div>
<p className="text-base text-gray-400">Opening Workspace …</p>
<div className="flex space-x-3 items-center text-left rounded-xl m-auto px-4 h-16 w-72 mt-4 mb-2 bg-gray-100 dark:bg-gray-800">
<div className="rounded-full w-3 h-3 text-sm bg-green-500">&nbsp;</div>
<div>
<p className="text-gray-700 dark:text-gray-200 font-semibold">{this.state.workspaceInstance.workspaceId}</p>
<a target="_parent" href={this.state.workspace?.contextURL}><p className="w-56 truncate hover:text-blue-600 dark:hover:text-blue-400" >{this.state.workspace?.contextURL}</p></a>
<p className="text-gray-700 dark:text-gray-200 font-semibold w-56 truncate">{this.state.workspaceInstance.workspaceId}</p>
<a target="_parent" href={contextURL}><p className="w-56 truncate hover:text-blue-600 dark:hover:text-blue-400" >{contextURL}</p></a>
</div>
</div>
{installationSteps}
<div className="mt-10 justify-center flex space-x-2">
<ContextMenu menuEntries={[
{
Expand All @@ -310,7 +325,18 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
]} >
<button className="secondary">More Actions...<Arrow up={false} /></button>
</ContextMenu>
<a target="_blank" href={this.state.desktopIde.link}><button>{this.state.desktopIde.label}</button></a>
<button onClick={() => {
let redirect = false;
try {
const desktopLink = new URL(openLink);
redirect = desktopLink.protocol != 'http:' && desktopLink.protocol != 'https:';
} catch {}
if (redirect) {
window.location.href = openLink;
} else {
window.open(openLink, '_blank', 'noopener');
}
}}>{openLinkLabel}</button>
</div>
<div className="text-sm text-gray-400 dark:text-gray-500 mt-5">These IDE options are based on <a className="gp-link" href={gitpodHostUrl.asPreferences().toString()} target="_parent">your user preferences</a>.</div>
</div>;
Expand All @@ -336,7 +362,7 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
<div className="rounded-full w-3 h-3 text-sm bg-gitpod-kumquat">&nbsp;</div>
<div>
<p className="text-gray-700 dark:text-gray-200 font-semibold">{this.state.workspaceInstance.workspaceId}</p>
<a target="_parent" href={ContextURL.parseToURL(this.state.workspace?.contextURL)?.toString()}><p className="w-56 truncate hover:text-blue-600 dark:hover:text-blue-400" >{this.state.workspace?.contextURL}</p></a>
<a target="_parent" href={contextURL}><p className="w-56 truncate hover:text-blue-600 dark:hover:text-blue-400" >{contextURL}</p></a>
</div>
</div>
<div className="mt-10 flex justify-center">
Expand All @@ -363,7 +389,7 @@ export default class StartWorkspace extends React.Component<StartWorkspaceProps,
<div className="rounded-full w-3 h-3 text-sm bg-gray-300">&nbsp;</div>
<div>
<p className="text-gray-700 dark:text-gray-200 font-semibold">{this.state.workspaceInstance.workspaceId}</p>
<a target="_parent" href={ContextURL.parseToURL(this.state.workspace?.contextURL)?.toString()}><p className="w-56 truncate hover:text-blue-600 dark:hover:text-blue-400" >{this.state.workspace?.contextURL}</p></a>
<a target="_parent" href={contextURL}><p className="w-56 truncate hover:text-blue-600 dark:hover:text-blue-400" >{contextURL}</p></a>
</div>
</div>
<PendingChangesDropdown workspaceInstance={this.state.workspaceInstance} />
Expand Down
4 changes: 3 additions & 1 deletion components/gitpod-protocol/src/context-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export namespace ContextURL {
export const INCREMENTAL_PREBUILD_PREFIX = "incremental-prebuild";
export const PREBUILD_PREFIX = "prebuild";
export const IMAGEBUILD_PREFIX = "imagebuild";
export const REFERRER_PREFIX = 'referrer:';

/**
* The field "contextUrl" might contain prefixes like:
Expand Down Expand Up @@ -37,7 +38,8 @@ export namespace ContextURL {
const firstSegment = segments[0];
if (firstSegment === PREBUILD_PREFIX ||
firstSegment === INCREMENTAL_PREBUILD_PREFIX ||
firstSegment === IMAGEBUILD_PREFIX) {
firstSegment === IMAGEBUILD_PREFIX ||
firstSegment.startsWith(REFERRER_PREFIX)) {
return segmentsToURL(1);
}

Expand Down
6 changes: 6 additions & 0 deletions components/gitpod-protocol/src/gitpod-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,14 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
getWorkspaceOwner(workspaceId: string): Promise<UserInfo | undefined>;
getWorkspaceUsers(workspaceId: string): Promise<WorkspaceInstanceUser[]>;
getFeaturedRepositories(): Promise<WhitelistedRepository[]>;
/**
* **Security:**
* Sensitive information like an owner token is erased, since it allows access for all team members.
* If you need to access an owner token use `getOwnerToken` instead.
*/
getWorkspace(id: string): Promise<WorkspaceInfo>;
isWorkspaceOwner(workspaceId: string): Promise<boolean>;
getOwnerToken(workspaceId: string): Promise<string>;

/**
* Creates and starts a workspace for the given context URL.
Expand Down
22 changes: 22 additions & 0 deletions components/gitpod-protocol/src/ide-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,28 @@ export interface IDEOptions {
* The default desktop IDE when the user has not specified one.
*/
defaultDesktopIde: string;

/**
* Client specific IDE options.
*/
clients?: { [id: string]: IDEClient };
}

export interface IDEClient {
/**
* The default desktop IDE when the user has not specified one.
*/
defaultDesktopIDE?: string;

/**
* Desktop IDEs supported by the client.
*/
desktopIDEs?: string[]

/**
* Steps to install the client on user machine.
*/
installationSteps?: string[]
}

export interface IDEOption {
Expand Down
12 changes: 12 additions & 0 deletions components/gitpod-protocol/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,18 @@ export namespace PrebuiltWorkspaceContext {
}
}

export interface WithReferrerContext extends WorkspaceContext {
referrer: string
referrerIde?: string
}

export namespace WithReferrerContext {
export function is(context: any): context is WithReferrerContext {
return context
&& 'referrer' in context;
}
}

export interface WithEnvvarsContext extends WorkspaceContext {
envvars: EnvVarWithValue[];
}
Expand Down
1 change: 1 addition & 0 deletions components/ide/code-desktop/status/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func main() {
response := make(map[string]string)
response["link"] = link.String()
response["label"] = label
response["clientID"] = schema
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
})
Expand Down
5 changes: 3 additions & 2 deletions components/server/ee/src/workspace/workspace-starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { Workspace, User, WorkspaceInstance, WorkspaceInstanceConfiguration, NamedWorkspaceFeatureFlag } from "@gitpod/gitpod-protocol";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { inject, injectable } from "inversify";
import { IDEConfig } from "../../../src/ide-config";
import { WorkspaceStarter } from "../../../src/workspace/workspace-starter";
Expand All @@ -19,8 +20,8 @@ export class WorkspaceStarterEE extends WorkspaceStarter {
*
* @param workspace the workspace to create an instance for
*/
protected async newInstance(workspace: Workspace, user: User, excludeFeatureFlags: NamedWorkspaceFeatureFlag[], ideConfig: IDEConfig): Promise<WorkspaceInstance> {
const instance = await super.newInstance(workspace, user, excludeFeatureFlags, ideConfig);
protected async newInstance(ctx: TraceContext, workspace: Workspace, user: User, excludeFeatureFlags: NamedWorkspaceFeatureFlag[], ideConfig: IDEConfig): Promise<WorkspaceInstance> {
const instance = await super.newInstance(ctx, workspace, user, excludeFeatureFlags, ideConfig);
if (await this.eligibilityService.hasFixedWorkspaceResources(user)) {
const config: WorkspaceInstanceConfiguration = instance.configuration!;
const ff = (config.featureFlags || []);
Expand Down
1 change: 1 addition & 0 deletions components/server/src/auth/rate-limiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig {
"getFeaturedRepositories": { group: "default", points: 1 },
"getWorkspace": { group: "default", points: 1 },
"isWorkspaceOwner": { group: "default", points: 1 },
"getOwnerToken": { group: "default", points: 1 },
"createWorkspace": { group: "default", points: 1 },
"startWorkspace": { group: "default", points: 1 },
"stopWorkspace": { group: "default", points: 1 },
Expand Down
2 changes: 2 additions & 0 deletions components/server/src/container-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import { IClientCallMetrics } from '@gitpod/content-service/lib/client-call-metr
import { DebugApp } from './debug-app';
import { LocalMessageBroker, LocalRabbitMQBackedMessageBroker } from './messaging/local-message-broker';
import { contentServiceBinder } from '@gitpod/content-service/lib/sugar';
import { ReferrerPrefixParser } from './workspace/referrer-prefix-context-parser';

export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
bind(Config).toConstantValue(ConfigFile.fromFile());
Expand Down Expand Up @@ -147,6 +148,7 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
bind(ContextParser).toSelf().inSingletonScope();
bind(SnapshotContextParser).toSelf().inSingletonScope();
bind(IContextParser).to(SnapshotContextParser).inSingletonScope();
bind(IPrefixContextParser).to(ReferrerPrefixParser).inSingletonScope();
bind(IPrefixContextParser).to(EnvvarPrefixParser).inSingletonScope();
bind(IPrefixContextParser).to(ImageBuildPrefixContextParser).inSingletonScope();
bind(IPrefixContextParser).to(AdditionalContentPrefixContextParser).inSingletonScope();
Expand Down
30 changes: 28 additions & 2 deletions components/server/src/ide-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { Disposable, DisposableCollection, Emitter } from '@gitpod/gitpod-protocol';
import { filePathTelepresenceAware } from '@gitpod/gitpod-protocol/lib/env';
import { IDEOptions } from '@gitpod/gitpod-protocol/lib/ide-protocol';
import { IDEClient, IDEOptions } from '@gitpod/gitpod-protocol/lib/ide-protocol';
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
import { repeat } from '@gitpod/gitpod-protocol/lib/util/repeat';
import * as Ajv from 'ajv';
Expand All @@ -19,6 +19,7 @@ import debounce = require('lodash.debounce')
export interface IDEConfig {
supervisorImage: string;
ideOptions: IDEOptions;
clients?: { [id: string]: IDEClient };
}

const scheme = {
Expand Down Expand Up @@ -56,13 +57,23 @@ const scheme = {
},
"defaultIde": { "type": "string" },
"defaultDesktopIde": { "type": "string" },
"clients": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"defaultDesktopIDE": { "type": "string" },
"desktopIDEs": { "type": "array", "items": { "type": "string" } },
}
}
}
},
"required": [
"options",
"defaultIde",
"defaultDesktopIde",
],
},
}
},
"required": [
"supervisorImage",
Expand Down Expand Up @@ -143,6 +154,21 @@ export class IDEConfigService {
throw new Error(`invalid: Editor (desktop), '${newValue.ideOptions.defaultDesktopIde}' needs to be of type 'desktop' but is '${newValue.ideOptions.options[newValue.ideOptions.defaultIde].type}'.`);
}

if (newValue.ideOptions.clients) {
for (const [clientId, client] of Object.entries(newValue.ideOptions.clients)) {
if (client.defaultDesktopIDE && !(client.defaultDesktopIDE in newValue.ideOptions.options)) {
throw new Error(`${clientId} client: there is no option entry for editor '${client.defaultDesktopIDE}'.`);
}
if (client.desktopIDEs) {
for (const ide of client.desktopIDEs) {
if (!(ide in newValue.ideOptions.options)) {
throw new Error(`${clientId} client: there is no option entry for editor '${ide}'.`);
}
}
}
}
}

value = newValue;
}

Expand Down
Loading