diff --git a/components/gitpod-protocol/go/gitpod-service.go b/components/gitpod-protocol/go/gitpod-service.go index edc26fd97205eb..ce8c9175172bc1 100644 --- a/components/gitpod-protocol/go/gitpod-service.go +++ b/components/gitpod-protocol/go/gitpod-service.go @@ -1899,7 +1899,9 @@ type WorkspaceInstanceStatus struct { // StartWorkspaceOptions is the StartWorkspaceOptions message type type StartWorkspaceOptions struct { - ForceDefaultImage bool `json:"forceDefaultImage,omitempty"` + ForceDefaultImage bool `json:"forceDefaultImage,omitempty"` + WorkspaceClass string `json:"workspaceClass,omitempty"` + IdeSettings *IDESettings `json:"ideSettings,omitempty"` } // GetWorkspaceTimeoutResult is the GetWorkspaceTimeoutResult message type diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index d0affefd45bace..6d4438890b2a79 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -26,6 +26,7 @@ import { PrebuiltWorkspace, UserSSHPublicKeyValue, SSHPublicKeyValue, + IDESettings, } from "./protocol"; import { Team, @@ -414,16 +415,20 @@ export namespace GitpodServer { export interface GetAccountStatementOptions { date?: string; } - export interface CreateWorkspaceOptions { + export interface CreateWorkspaceOptions extends StartWorkspaceOptions { contextUrl: string; + // whether running workspaces on the same context should be ignored. If false (default) users will be asked. ignoreRunningWorkspaceOnSameCommit?: boolean; ignoreRunningPrebuild?: boolean; allowUsingPreviousPrebuilds?: boolean; forceDefaultConfig?: boolean; } + export interface StartWorkspaceOptions { - forceDefaultImage: boolean; + forceDefaultImage?: boolean; + workspaceClass?: string; + ideSettings?: IDESettings; } export interface TakeSnapshotOptions { workspaceId: string; diff --git a/components/server/src/ide-service.ts b/components/server/src/ide-service.ts index 1a843d3202fad9..c5269ac8e2666d 100644 --- a/components/server/src/ide-service.ts +++ b/components/server/src/ide-service.ts @@ -84,18 +84,28 @@ export class IDEService { return newIDESettings; } - async resolveWorkspaceConfig(workspace: Workspace, user: User): Promise { + async resolveWorkspaceConfig( + workspace: Workspace, + user: User, + userSelectedIdeSettings?: IDESettings, + ): Promise { const use = await this.configCatClientFactory().getValueAsync("use_IDEService_ResolveWorkspaceConfig", false, { user, }); if (use) { - return this.doResolveWorkspaceConfig(workspace, user); + return this.doResolveWorkspaceConfig( + workspace, + userSelectedIdeSettings || user.additionalData?.ideSettings, + ); } const deprecated = await this.resolveDeprecated(workspace, user); // assert against ide-service (async () => { - const config = await this.doResolveWorkspaceConfig(workspace, user); + const config = await this.doResolveWorkspaceConfig( + workspace, + userSelectedIdeSettings || user.additionalData?.ideSettings, + ); const { tasks: configTasks, ...newConfig } = config; const { tasks: deprecatedTasks, ...newDeprecated } = deprecated; // we omit tasks because we're going to rewrite them soon and the deepEqual was failing @@ -104,14 +114,17 @@ export class IDEService { return deprecated; } - private async doResolveWorkspaceConfig(workspace: Workspace, user: User): Promise { + private async doResolveWorkspaceConfig( + workspace: Workspace, + userSelectedIdeSettings?: IDESettings, + ): Promise { const workspaceType = workspace.type === "prebuild" ? IdeServiceApi.WorkspaceType.PREBUILD : IdeServiceApi.WorkspaceType.REGULAR; const req: IdeServiceApi.ResolveWorkspaceConfigRequest = { type: workspaceType, context: JSON.stringify(workspace.context), - ideSettings: JSON.stringify(user.additionalData?.ideSettings), + ideSettings: JSON.stringify(userSelectedIdeSettings), workspaceConfig: JSON.stringify(workspace.config), }; for (let attempt = 0; attempt < 15; attempt++) { diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 8153070162ce43..7d3623e81d6763 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -734,9 +734,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { await projectPromise, await userEnvVars, await projectEnvVarsPromise, - { - forceDefaultImage: !!options.forceDefaultImage, - }, + options, ); traceWI(ctx, { instanceId: result.instanceID }); return result; @@ -1207,6 +1205,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { project, await envVars, await projectEnvVarsPromise, + options, ); ctx.span?.log({ event: "startWorkspaceComplete", ...startWorkspaceResult }); diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index 1d9e94fdb50d41..9b92d9f846af40 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -60,6 +60,8 @@ import { EnvVarWithValue, BillingTier, Project, + GitpodServer, + IDESettings, } from "@gitpod/gitpod-protocol"; import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; @@ -127,9 +129,8 @@ import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution"; import { LogContext } from "@gitpod/gitpod-protocol/lib/util/logging"; import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat"; -export interface StartWorkspaceOptions { +export interface StartWorkspaceOptions extends GitpodServer.StartWorkspaceOptions { rethrow?: boolean; - forceDefaultImage?: boolean; excludeFeatureFlags?: NamedWorkspaceFeatureFlag[]; } @@ -139,21 +140,31 @@ const INSTANCE_START_RETRY_INTERVAL_SECONDS = 2; export async function getWorkspaceClassForInstance( ctx: TraceContext, workspace: Workspace, + previousInstance: WorkspaceInstance | undefined, user: User, project: Project | undefined, + workspaceClassOverride: string | undefined, entitlementService: EntitlementService, config: WorkspaceClassesConfig, ): Promise { const span = TraceContext.startSpan("getWorkspaceClassForInstance", ctx); try { let workspaceClass: string | undefined; - switch (workspace.type) { - case "prebuild": - workspaceClass = project?.settings?.workspaceClasses?.prebuild; - break; - case "regular": - workspaceClass = project?.settings?.workspaceClasses?.regular; - break; + if (workspaceClassOverride) { + workspaceClass = workspaceClassOverride; + } + if (!workspaceClass && previousInstance) { + workspaceClass = previousInstance.workspaceClass; + } + if (!workspaceClass) { + switch (workspace.type) { + case "prebuild": + workspaceClass = project?.settings?.workspaceClasses?.prebuild; + break; + case "regular": + workspaceClass = project?.settings?.workspaceClasses?.regular; + break; + } } if (!workspaceClass && (await entitlementService.userGetsMoreResources(user))) { workspaceClass = config.find((c) => !!c.marker?.moreResources)?.id; @@ -269,7 +280,7 @@ export class WorkspaceStarter { } } - const ideConfig = await this.resolveIDEConfiguration(ctx, workspace, user); + const ideConfig = await this.resolveIDEConfiguration(ctx, workspace, user, options.ideSettings); // create and store instance let instance = await this.workspaceDb @@ -283,6 +294,7 @@ export class WorkspaceStarter { project, options.excludeFeatureFlags || [], ideConfig, + options.workspaceClass, ), ); span.log({ newInstance: instance.id }); @@ -350,7 +362,12 @@ export class WorkspaceStarter { } } - private async resolveIDEConfiguration(ctx: TraceContext, workspace: Workspace, user: User) { + private async resolveIDEConfiguration( + ctx: TraceContext, + workspace: Workspace, + user: User, + userSelectedIdeSettings?: IDESettings, + ) { const span = TraceContext.startSpan("resolveIDEConfiguration", ctx); try { const migrated = this.ideService.migrateSettings(user); @@ -358,7 +375,7 @@ export class WorkspaceStarter { user.additionalData.ideSettings = migrated; } - const resp = await this.ideService.resolveWorkspaceConfig(workspace, user); + const resp = await this.ideService.resolveWorkspaceConfig(workspace, user, userSelectedIdeSettings); if (!user.additionalData?.ideSettings && resp.refererIde) { // A user does not have IDE settings configured yet configure it with a referrer ide as default. const additionalData = user?.additionalData || {}; @@ -770,6 +787,7 @@ export class WorkspaceStarter { project: Project | undefined, excludeFeatureFlags: NamedWorkspaceFeatureFlag[], ideConfig: IdeServiceApi.ResolveWorkspaceConfigResponse, + workspaceClassOverride?: string, ): Promise { const span = TraceContext.startSpan("newInstance", ctx); try { @@ -814,8 +832,10 @@ export class WorkspaceStarter { let workspaceClass = await getWorkspaceClassForInstance( ctx, workspace, + previousInstance, user, project, + workspaceClassOverride, this.entitlementService, this.config.workspaceClasses, );