Skip to content
Closed
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
11 changes: 11 additions & 0 deletions components/gitpod-protocol/data/gitpod-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,17 @@
"description": "the hard limit acts as a ceiling for the soft limit. For more details please check https://man7.org/linux/man-pages/man2/getrlimit.2.html"
}
}
},
"workspaceRequirements": {
"type": "object",
"description": "Configure the requirements of the workspace.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we talk about requirements. But when those requirements are not satisfied, we fall back to the default. Under this behaviour, it's no longer a requirement, it's a preference.

Can we change the name to workspacePreferences?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly worth adding info about what happens when no workspace class matches in here as well.

"additionalProperties": false,
"properties": {
"class": {
"type": ["string", "array"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this overloaded option makes it unnecessarily complicated. I feel the UX wouldn't suffer much if we always made it a list.

Most of the time, I expect customers would be copying this from the docs so it doesn't matter to them if it's a list or just a string.

"description": "The class that should be used for the workspace."
}
}
}
},
"additionalProperties": false,
Expand Down
65 changes: 65 additions & 0 deletions components/gitpod-protocol/go/gitpod-config-types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions components/gitpod-protocol/src/gitpod-file-parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,27 @@ gitConfig:
image: DEFAULT_IMAGE,
});
}

@test public testSingleWorkspaceClass() {
const content = `workspaceRequirements: \n class: g1-standard`;
const result = this.parser.parse(content, {}, DEFAULT_CONFIG);
expect(result.config).to.deep.equal({
workspaceRequirements: {
class: "g1-standard",
},
image: DEFAULT_IMAGE,
});
}

@test public testMultiWorkspaceClass() {
const content = `workspaceRequirements: \n class:\n` + ` - g1-standard\n` + ` - g1-large`;
const result = this.parser.parse(content, {}, DEFAULT_CONFIG);
expect(result.config).to.deep.equal({
workspaceRequirements: {
class: ["g1-standard", "g1-large"],
},
image: DEFAULT_IMAGE,
});
}
}
module.exports = new TestGitpodFileParser(); // Only to circumvent no usage warning :-/
5 changes: 5 additions & 0 deletions components/gitpod-protocol/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,10 @@ export interface CoreDumpConfig {
hardLimit?: number;
}

export interface WorkspaceRequirements {
class?: string | string[];
}

export interface WorkspaceConfig {
mainConfiguration?: string;
additionalRepositories?: RepositoryCloneInformation[];
Expand All @@ -831,6 +835,7 @@ export interface WorkspaceConfig {
vscode?: VSCodeConfig;
jetbrains?: JetBrainsConfig;
coreDump?: CoreDumpConfig;
workspaceRequirements?: WorkspaceRequirements;

/** deprecated. Enabled by default **/
experimentalNetwork?: boolean;
Expand Down
20 changes: 19 additions & 1 deletion components/server/src/workspace/workspace-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { WorkspaceDB } from "@gitpod/gitpod-db/lib";
import { User, Workspace } from "@gitpod/gitpod-protocol";
import { User, Workspace, WorkspaceRequirements } from "@gitpod/gitpod-protocol";
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
import { EntitlementService } from "../billing/entitlement-service";
Expand Down Expand Up @@ -214,4 +214,22 @@ export namespace WorkspaceClasses {
}
}
}

export function fromGitpodConfigOrDefault(
classes: WorkspaceClassesConfig,
requirements: WorkspaceRequirements | undefined,
defaultClass: string,
): string {
let selected: string | undefined = undefined;
if (requirements?.class) {
if (Array.isArray(requirements.class)) {
selected = requirements.class.find((r) => classes.filter((c) => !c.deprecated).some((c) => r === c.id));
} else {
const singleClass = requirements.class as string;
selected = classes.filter((c) => !c.deprecated).find((c) => singleClass === c.id)?.id;
}
}

return selected ?? defaultClass;
}
}
54 changes: 46 additions & 8 deletions components/server/src/workspace/workspace-starter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@

import { DBWithTracing, MaybeWorkspaceInstance, WorkspaceDB } from "@gitpod/gitpod-db/lib";
import { WorkspaceClassesConfig } from "./workspace-classes";
import { PrebuiltWorkspace, User, Workspace, WorkspaceInstance, WorkspaceType } from "@gitpod/gitpod-protocol";
import {
PrebuiltWorkspace,
User,
Workspace,
WorkspaceConfig,
WorkspaceInstance,
WorkspaceRequirements,
WorkspaceType,
} from "@gitpod/gitpod-protocol";
import { IDEOption, IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol";
import * as chai from "chai";
import { migrationIDESettings, chooseIDE, getWorkspaceClassForInstance } from "./workspace-starter";
Expand Down Expand Up @@ -301,19 +309,36 @@ describe("workspace-starter", function () {
await execute(builder, "g1-standard");
});

it("new prebuild workspace, prebuild class configured", async function () {
const builder = new WorkspaceClassTestBuilder("prebuild").withPrebuildClassConfigured("g1-large");
it("new prebuild workspace, prebuild class not configured, does not have more resources", async function () {
const builder = new WorkspaceClassTestBuilder("prebuild");
await execute(builder, "g1-standard");
});

it("new prebuild workspace, prebuild class not configured, gitpod.yaml has g1-large", async function () {
const requirements: WorkspaceRequirements = {
class: "g1-large",
};

const builder = new WorkspaceClassTestBuilder("prebuild").withWorkspaceRequirements(requirements);
await execute(builder, "g1-large");
});

it("new prebuild workspace, prebuild class not configured, has more resources", async function () {
const builder = new WorkspaceClassTestBuilder("prebuild").withHasMoreResources();
it("new prebuild workspace, prebuild class not configured, first valid class from gitpod.yml", async function () {
const requirements: WorkspaceRequirements = {
class: ["unsupported", "g1-large"],
};

const builder = new WorkspaceClassTestBuilder("prebuild").withWorkspaceRequirements(requirements);
await execute(builder, "g1-large");
});

it("new prebuild workspace, prebuild class not configured, does not have more resources", async function () {
const builder = new WorkspaceClassTestBuilder("prebuild");
await execute(builder, "g1-standard");
it("new regular workspace, class not configured, gitpod.yml has workspace class configured", async function () {
const requirements: WorkspaceRequirements = {
class: "g1-large",
};

const builder = new WorkspaceClassTestBuilder("regular").withWorkspaceRequirements(requirements);
await execute(builder, "g1-large");
});
});
});
Expand Down Expand Up @@ -352,6 +377,9 @@ class WorkspaceClassTestBuilder {
// User has more resources
hasMoreResources: boolean;

// Requirements for the workspace
workspaceRequirements: WorkspaceRequirements;

constructor(workspaceType: WorkspaceType) {
this.workspaceType = workspaceType;
}
Expand Down Expand Up @@ -381,6 +409,11 @@ class WorkspaceClassTestBuilder {
return this;
}

public withWorkspaceRequirements(req: WorkspaceRequirements): WorkspaceClassTestBuilder {
this.workspaceRequirements = req;
return this;
}

public build(): [
TraceContext,
Workspace,
Expand All @@ -396,9 +429,14 @@ class WorkspaceClassTestBuilder {
span,
};

const workspaceConfig: WorkspaceConfig = {
workspaceRequirements: this.workspaceRequirements,
};

const workspace: Workspace = {
basedOnPrebuildId: this.basedOnPrebuild,
type: this.workspaceType,
config: workspaceConfig,
} as Workspace;

const previousInstance: WorkspaceInstance = {
Expand Down
30 changes: 18 additions & 12 deletions components/server/src/workspace/workspace-starter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,41 +203,47 @@ export async function getWorkspaceClassForInstance(
previousInstance: WorkspaceInstance | undefined,
user: User,
entitlementService: EntitlementService,
config: WorkspaceClassesConfig,
classes: WorkspaceClassesConfig,
workspaceDb: DBWithTracing<WorkspaceDB>,
): Promise<string> {
const span = TraceContext.startSpan("getWorkspaceClassForInstance", ctx);
try {
let workspaceClass = "";
let workspaceClass: string | undefined = "";
if (!previousInstance?.workspaceClass) {
if (workspace.type == "regular") {
const prebuildClass = await WorkspaceClasses.getFromPrebuild(ctx, workspace, workspaceDb.trace(ctx));
if (prebuildClass) {
const userClass = await WorkspaceClasses.getConfiguredOrUpgradeFromLegacy(
user,
config,
classes,
entitlementService,
);
workspaceClass = WorkspaceClasses.selectClassForRegular(prebuildClass, userClass, config);
} else if (user.additionalData?.workspaceClasses?.regular) {
workspaceClass = user.additionalData?.workspaceClasses?.regular;
workspaceClass = WorkspaceClasses.selectClassForRegular(prebuildClass, userClass, classes);
} else {
workspaceClass = WorkspaceClasses.fromGitpodConfigOrDefault(
classes,
workspace.config.workspaceRequirements,
await WorkspaceClasses.getConfiguredOrUpgradeFromLegacy(user, classes, entitlementService),
);
}
}

if (workspace.type == "prebuild") {
if (user.additionalData?.workspaceClasses?.prebuild) {
workspaceClass = user.additionalData?.workspaceClasses?.prebuild;
}
workspaceClass = WorkspaceClasses.fromGitpodConfigOrDefault(
classes,
workspace.config.workspaceRequirements,
WorkspaceClasses.getDefaultId(classes),
);
}

if (!workspaceClass) {
workspaceClass = WorkspaceClasses.getDefaultId(config);
workspaceClass = WorkspaceClasses.getDefaultId(classes);
if (await entitlementService.userGetsMoreResources(user)) {
workspaceClass = WorkspaceClasses.getMoreResourcesIdOrDefault(config);
workspaceClass = WorkspaceClasses.getMoreResourcesIdOrDefault(classes);
}
}
} else {
workspaceClass = WorkspaceClasses.getPreviousOrDefault(config, previousInstance.workspaceClass);
workspaceClass = WorkspaceClasses.getPreviousOrDefault(classes, previousInstance.workspaceClass);
}

return workspaceClass;
Expand Down