Skip to content

Set workspace class based on user preference #11313

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 7 commits into from
Jul 21, 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
4 changes: 4 additions & 0 deletions components/server/debug.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash

set -Eeuo pipefail
source /workspace/gitpod/scripts/ws-deploy.sh deployment server
11 changes: 10 additions & 1 deletion components/server/ee/src/workspace/workspace-starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,21 @@ export class WorkspaceStarterEE extends WorkspaceStarter {
protected async newInstance(
ctx: TraceContext,
workspace: Workspace,
previousInstance: WorkspaceInstance | undefined,
user: User,
excludeFeatureFlags: NamedWorkspaceFeatureFlag[],
ideConfig: IDEConfig,
forcePVC: boolean,
): Promise<WorkspaceInstance> {
const instance = await super.newInstance(ctx, workspace, user, excludeFeatureFlags, ideConfig, forcePVC);
const instance = await super.newInstance(
ctx,
workspace,
previousInstance,
user,
excludeFeatureFlags,
ideConfig,
forcePVC,
);
if (await this.eligibilityService.hasFixedWorkspaceResources(user)) {
const config: WorkspaceInstanceConfiguration = instance.configuration!;
const ff = config.featureFlags || [];
Expand Down
9 changes: 9 additions & 0 deletions components/server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as fs from "fs";
import * as yaml from "js-yaml";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { filePathTelepresenceAware } from "@gitpod/gitpod-protocol/lib/env";
import { WorkspaceClasses, WorkspaceClassesConfig } from "./workspace/workspace-classes";

export const Config = Symbol("Config");
export type Config = Omit<
Expand Down Expand Up @@ -184,6 +185,11 @@ export interface ConfigSerialized {
* considered inactive.
*/
inactivityPeriodForRepos?: number;

/**
* Supported workspace classes
*/
workspaceClasses: WorkspaceClassesConfig;
}

export namespace ConfigFile {
Expand Down Expand Up @@ -266,6 +272,9 @@ export namespace ConfigFile {
inactivityPeriodForRepos = config.inactivityPeriodForRepos;
}
}

WorkspaceClasses.validate(config.workspaceClasses);

return {
...config,
hostUrl,
Expand Down
105 changes: 105 additions & 0 deletions components/server/src/workspace/workspace-classes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Copyright (c) 2022 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 { log } from "@gitpod/gitpod-protocol/lib/util/logging";

export type WorkspaceClassesConfig = [WorkspaceClassConfig];

export interface WorkspaceClassConfig {
// The technical string we use to identify the class with internally
id: string;

// Is the "default" class. The config is validated to only every have exactly _one_ default class.
isDefault: boolean;

// The string we display to users in the UI
displayName: string;

// Whether or not to:
// - offer users this Workspace class for selection
// - use this class to start workspaces with. If a user has a class marked like this configured and starts a workspace they get the default class instead.
deprecated: boolean;

// Marks this class to have special semantics
marker?: {
// Marks this class as the one that users marked with "GetMoreResources" receive
moreResources: boolean;
};
}

export namespace WorkspaceClasses {
/**
* @param workspaceClasses
* @return The WorkspaceClass ID of the first class that is marked with "moreResources" (and not deprecated). Falls back to "getDefaultId()".
*/
export function getMoreResourcesIdOrDefault(workspaceClasses: WorkspaceClassesConfig): string {
const moreResources = workspaceClasses.filter((c) => !c.deprecated).find((c) => !!c.marker?.moreResources);
if (moreResources) {
return moreResources.id;
}

// fallback: default
return getDefaultId(workspaceClasses);
}

/**
* @param workspaceClasses
* @return The WorkspaceClass ID of the "default" class
*/
export function getDefaultId(workspaceClasses: WorkspaceClassesConfig): string {
validate(workspaceClasses);

return workspaceClasses.filter((c) => !c.deprecated).find((c) => c.isDefault)!.id;
}

/**
* Checks that the given workspaceClass is:
* - still configured
* - not deprecated
* If any of that is the case, it returns the default class
*
* @param workspaceClasses
* @param previousWorkspaceClass
*/
export function getPreviousOrDefault(
workspaceClasses: WorkspaceClassesConfig,
previousWorkspaceClass: string | undefined,
): string {
if (!previousWorkspaceClass) {
return getDefaultId(workspaceClasses);
}

const config = workspaceClasses.find((c) => c.id === previousWorkspaceClass);
if (!config) {
log.error(
`Found previous instance with workspace class '${previousWorkspaceClass}' which is no longer configured! Falling back to default class.`,
{ workspaceClasses },
);
return getDefaultId(workspaceClasses);
}
if (config.deprecated) {
log.info(
`Found previous instance with workspace class '${previousWorkspaceClass}' which is deprecated. Falling back to default class.`,
{ workspaceClasses },
);
return getDefaultId(workspaceClasses);
}
return config.id;
}

export function validate(workspaceClasses: WorkspaceClassesConfig): void {
const defaultClasses = workspaceClasses
.filter((c) => !c.deprecated)
.map((c) => (c.isDefault ? 1 : 0))
.reduce((acc: number, isDefault: number) => (acc + isDefault) as number, 0);

if (defaultClasses !== 1) {
throw new Error(
"Exactly one default workspace class needs to be configured:" + JSON.stringify(defaultClasses),
);
}
}
}
1 change: 1 addition & 0 deletions components/server/src/workspace/workspace-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export class WorkspaceFactory {

const id = await this.generateWorkspaceID(context);
const date = new Date().toISOString();

const newWs = <Workspace>{
id,
type: "regular",
Expand Down
Loading