diff --git a/components/gitpod-protocol/src/context-url.ts b/components/gitpod-protocol/src/context-url.ts index 3fe1b4e8b97209..00ca3a5e771eac 100644 --- a/components/gitpod-protocol/src/context-url.ts +++ b/components/gitpod-protocol/src/context-url.ts @@ -20,6 +20,7 @@ export namespace ContextURL { export const IMAGEBUILD_PREFIX = "imagebuild"; export const SNAPSHOT_PREFIX = "snapshot"; export const REFERRER_PREFIX = "referrer:"; + export const EDITOR_PREFIX = "editor:"; /** * This function will (try to) return the HTTP(S) URL of the context the user originally created this workspace on. @@ -94,7 +95,8 @@ export namespace ContextURL { firstSegment === INCREMENTAL_PREBUILD_PREFIX || firstSegment === IMAGEBUILD_PREFIX || firstSegment === SNAPSHOT_PREFIX || - firstSegment.startsWith(REFERRER_PREFIX) + firstSegment.startsWith(REFERRER_PREFIX) || + firstSegment.startsWith(EDITOR_PREFIX) ) { return segmentsToURL(1); } diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index 85c6f373701839..2bff460862d959 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -863,6 +863,17 @@ export namespace PrebuiltWorkspaceContext { } } +export interface WithEditorContext extends WorkspaceContext { + ide: string; + useLatest?: boolean; +} + +export namespace WithEditorContext { + export function is(context: any): context is WithEditorContext { + return context && "ide" in context; + } +} + export interface WithReferrerContext extends WorkspaceContext { referrer: string; referrerIde?: string; diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts index e4ac066622fe52..851858e418f381 100644 --- a/components/server/src/container-module.ts +++ b/components/server/src/container-module.ts @@ -96,6 +96,7 @@ 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"; +import { EditorPrefixParser } from "./workspace/editor-prefix-context-parser"; import { InstallationAdminTelemetryDataProvider } from "./installation-admin/telemetry-data-provider"; export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { @@ -169,6 +170,7 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(SnapshotContextParser).toSelf().inSingletonScope(); bind(IContextParser).to(SnapshotContextParser).inSingletonScope(); bind(IPrefixContextParser).to(ReferrerPrefixParser).inSingletonScope(); + bind(IPrefixContextParser).to(EditorPrefixParser).inSingletonScope(); bind(IPrefixContextParser).to(EnvvarPrefixParser).inSingletonScope(); bind(IPrefixContextParser).to(ImageBuildPrefixContextParser).inSingletonScope(); bind(IPrefixContextParser).to(AdditionalContentPrefixContextParser).inSingletonScope(); diff --git a/components/server/src/workspace/editor-prefix-context-parser.ts b/components/server/src/workspace/editor-prefix-context-parser.ts new file mode 100644 index 00000000000000..7dab11df780ed4 --- /dev/null +++ b/components/server/src/workspace/editor-prefix-context-parser.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2020 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { IPrefixContextParser } from "./context-parser"; +import { User, WorkspaceContext, WithEditorContext } from "@gitpod/gitpod-protocol"; +import { injectable } from "inversify"; + +@injectable() +export class EditorPrefixParser implements IPrefixContextParser { + private readonly prefix = /^\/?editor:([^\/:]*?)(?::([^\/:]*?))?\//; + + findPrefix(_: User, context: string): string | undefined { + return this.prefix.exec(context)?.[0] || undefined; + } + + async handle(_: User, prefix: string, context: WorkspaceContext): Promise { + const matches = this.prefix.exec(prefix); + const ide = matches?.[1]; + const useLatest = matches?.[2] === "latest"; + return ide ? { ...context, ide, useLatest } : context; + } +} diff --git a/components/server/src/workspace/workspace-starter.spec.ts b/components/server/src/workspace/workspace-starter.spec.ts index 5161477e8fcf6d..12fd1b290361ae 100644 --- a/components/server/src/workspace/workspace-starter.spec.ts +++ b/components/server/src/workspace/workspace-starter.spec.ts @@ -249,5 +249,19 @@ describe("workspace-starter", function () { const result = chooseIDE("unknown-custom", customOptions, useLatest, hasPerm); expect(result.ideImage).to.equal(ideOptions.options["code"].latestImage); }); + + it("not exists ide with custom permission", function () { + const useLatest = true; + const hasPerm = true; + const result = chooseIDE("not-exists", ideOptions, useLatest, hasPerm); + expect(result.ideImage).to.equal(ideOptions.options["code"].latestImage); + }); + + it("not exists ide with custom permission", function () { + const useLatest = true; + const hasPerm = false; + const result = chooseIDE("not-exists", ideOptions, useLatest, hasPerm); + expect(result.ideImage).to.equal(ideOptions.options["code"].latestImage); + }); }); }); diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index 717de3be973bb7..87305c7d63d4f5 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -103,7 +103,7 @@ import * as path from "path"; import * as grpc from "@grpc/grpc-js"; import { IDEConfig, IDEConfigService } from "../ide-config"; import { EnvVarWithValue } from "@gitpod/gitpod-protocol/src/protocol"; -import { WithReferrerContext } from "@gitpod/gitpod-protocol/lib/protocol"; +import { WithEditorContext, WithReferrerContext } from "@gitpod/gitpod-protocol/lib/protocol"; import { IDEOption, IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol"; import { Deferred } from "@gitpod/gitpod-protocol/lib/util/deferred"; import { ExtendedUser } from "@gitpod/ws-manager/lib/constraints"; @@ -156,7 +156,7 @@ export const chooseIDE = ( const data: { desktopIdeImage?: string; ideImage: string } = { ideImage: defaultIdeImage, }; - const chooseOption = ideOptions.options[ideChoice]; + const chooseOption = ideOptions.options[ideChoice] ?? defaultIDEOption; const isDesktopIde = chooseOption.type === "desktop"; if (isDesktopIde) { data.desktopIdeImage = useLatest ? chooseOption?.latestImage ?? chooseOption?.image : chooseOption?.image; @@ -607,8 +607,12 @@ export class WorkspaceStarter { user.additionalData.ideSettings = migratted; } - const ideChoice = user.additionalData?.ideSettings?.defaultIde; - const useLatest = !!user.additionalData?.ideSettings?.useLatestVersion; + const preferenceIDE = user.additionalData?.ideSettings?.defaultIde; + const perferenceUseLatest = !!user.additionalData?.ideSettings?.useLatestVersion; + const ideChoice = WithEditorContext.is(workspace.context) ? workspace.context.ide : preferenceIDE; + const useLatest = WithEditorContext.is(workspace.context) + ? workspace.context.useLatest ?? perferenceUseLatest + : perferenceUseLatest; // TODO(cw): once we allow changing the IDE in the workspace config (i.e. .gitpod.yml), we must // give that value precedence over the default choice. @@ -618,7 +622,7 @@ export class WorkspaceStarter { ideConfig: { // We only check user setting because if code(insider) but desktopIde has no latestImage // it still need to notice user that this workspace is using latest IDE - useLatest: user.additionalData?.ideSettings?.useLatestVersion, + useLatest, }, }; @@ -635,9 +639,10 @@ export class WorkspaceStarter { const referrerIde = this.resolveReferrerIDE(workspace, user, ideConfig); if (referrerIde) { - configuration.desktopIdeImage = useLatest + configuration.desktopIdeImage = perferenceUseLatest ? referrerIde.option.latestImage ?? referrerIde.option.image : referrerIde.option.image; + configuration.ideConfig!.useLatest = perferenceUseLatest; if (!user.additionalData?.ideSettings) { // A user does not have IDE settings configured yet configure it with a referrer ide as default. const additionalData = user?.additionalData || {};