Skip to content

Commit 3e46051

Browse files
committed
[server] Move remaining trivial RPC away from EE
1 parent 67032b4 commit 3e46051

File tree

2 files changed

+126
-167
lines changed

2 files changed

+126
-167
lines changed

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 2 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,23 @@ import {
1212
User,
1313
Team,
1414
Permission,
15-
GetWorkspaceTimeoutResult,
16-
WorkspaceTimeoutDuration,
17-
SetWorkspaceTimeoutResult,
1815
WorkspaceContext,
1916
WorkspaceCreationResult,
2017
PrebuiltWorkspaceContext,
2118
CommitContext,
2219
PrebuiltWorkspace,
23-
WorkspaceInstance,
2420
ProviderRepository,
2521
PrebuildWithStatus,
2622
CreateProjectParams,
2723
Project,
2824
StartPrebuildResult,
2925
ClientHeaderFields,
3026
FindPrebuildsParams,
31-
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
3227
PrebuildEvent,
3328
OpenPrebuildContext,
3429
} from "@gitpod/gitpod-protocol";
3530
import { ResponseError } from "vscode-jsonrpc";
36-
import {
37-
AdmissionLevel,
38-
ControlAdmissionRequest,
39-
DescribeWorkspaceRequest,
40-
SetTimeoutRequest,
41-
} from "@gitpod/ws-manager/lib";
31+
import { AdmissionLevel, ControlAdmissionRequest } from "@gitpod/ws-manager/lib";
4232
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
4333
import { log, LogContext } from "@gitpod/gitpod-protocol/lib/util/logging";
4434
import { LicenseValidationResult } from "@gitpod/gitpod-protocol/lib/license-protocol";
@@ -62,7 +52,7 @@ import { ClientMetadata, traceClientMetadata } from "../../../src/websocket/webs
6252
import { BitbucketAppSupport } from "../bitbucket/bitbucket-app-support";
6353
import { URL } from "url";
6454
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
65-
import { EntitlementService, MayStartWorkspaceResult } from "../../../src/billing/entitlement-service";
55+
import { EntitlementService } from "../../../src/billing/entitlement-service";
6656
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
6757
import { BillingModes } from "../billing/billing-mode";
6858
import { UsageServiceDefinition } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
@@ -155,149 +145,10 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
155145
return allProjects;
156146
}
157147

158-
protected async mayStartWorkspace(
159-
ctx: TraceContext,
160-
user: User,
161-
organizationId: string | undefined,
162-
runningInstances: Promise<WorkspaceInstance[]>,
163-
): Promise<void> {
164-
await super.mayStartWorkspace(ctx, user, organizationId, runningInstances);
165-
166-
let result: MayStartWorkspaceResult = {};
167-
try {
168-
result = await this.entitlementService.mayStartWorkspace(
169-
user,
170-
organizationId,
171-
new Date(),
172-
runningInstances,
173-
);
174-
TraceContext.addNestedTags(ctx, { mayStartWorkspace: { result } });
175-
} catch (err) {
176-
log.error({ userId: user.id }, "EntitlementSerivce.mayStartWorkspace error", err);
177-
TraceContext.setError(ctx, err);
178-
return; // we don't want to block workspace starts because of internal errors
179-
}
180-
if (!!result.needsVerification) {
181-
throw new ResponseError(ErrorCodes.NEEDS_VERIFICATION, `Please verify your account.`);
182-
}
183-
if (!!result.usageLimitReachedOnCostCenter) {
184-
throw new ResponseError(ErrorCodes.PAYMENT_SPENDING_LIMIT_REACHED, "Increase usage limit and try again.", {
185-
attributionId: result.usageLimitReachedOnCostCenter,
186-
});
187-
}
188-
if (!!result.hitParallelWorkspaceLimit) {
189-
throw new ResponseError(
190-
ErrorCodes.TOO_MANY_RUNNING_WORKSPACES,
191-
`You cannot run more than ${result.hitParallelWorkspaceLimit.max} workspaces at the same time. Please stop a workspace before starting another one.`,
192-
);
193-
}
194-
}
195-
196148
async validateLicense(ctx: TraceContext): Promise<LicenseValidationResult> {
197149
return { valid: true };
198150
}
199151

200-
goDurationToHumanReadable(goDuration: string): string {
201-
const [, value, unit] = goDuration.match(/^(\d+)([mh])$/)!;
202-
let duration = parseInt(value);
203-
204-
switch (unit) {
205-
case "m":
206-
duration *= 60;
207-
break;
208-
case "h":
209-
duration *= 60 * 60;
210-
break;
211-
}
212-
213-
const hours = Math.floor(duration / 3600);
214-
duration %= 3600;
215-
const minutes = Math.floor(duration / 60);
216-
duration %= 60;
217-
218-
let result = "";
219-
if (hours) {
220-
result += `${hours} hour${hours === 1 ? "" : "s"}`;
221-
if (minutes) {
222-
result += " and ";
223-
}
224-
}
225-
if (minutes) {
226-
result += `${minutes} minute${minutes === 1 ? "" : "s"}`;
227-
}
228-
229-
return result;
230-
}
231-
232-
public async setWorkspaceTimeout(
233-
ctx: TraceContext,
234-
workspaceId: string,
235-
duration: WorkspaceTimeoutDuration,
236-
): Promise<SetWorkspaceTimeoutResult> {
237-
traceAPIParams(ctx, { workspaceId, duration });
238-
traceWI(ctx, { workspaceId });
239-
240-
const user = this.checkUser("setWorkspaceTimeout");
241-
242-
if (!(await this.entitlementService.maySetTimeout(user, new Date()))) {
243-
throw new ResponseError(ErrorCodes.PLAN_PROFESSIONAL_REQUIRED, "Plan upgrade is required");
244-
}
245-
246-
let validatedDuration;
247-
try {
248-
validatedDuration = WorkspaceTimeoutDuration.validate(duration);
249-
} catch (err) {
250-
throw new ResponseError(ErrorCodes.INVALID_VALUE, "Invalid duration : " + err.message);
251-
}
252-
253-
const workspace = await this.internalGetWorkspace(workspaceId, this.workspaceDb.trace(ctx));
254-
const runningInstances = await this.workspaceDb.trace(ctx).findRegularRunningInstances(user.id);
255-
const runningInstance = runningInstances.find((i) => i.workspaceId === workspaceId);
256-
if (!runningInstance) {
257-
throw new ResponseError(ErrorCodes.NOT_FOUND, "Can only set keep-alive for running workspaces");
258-
}
259-
await this.guardAccess({ kind: "workspaceInstance", subject: runningInstance, workspace: workspace }, "update");
260-
261-
const client = await this.workspaceManagerClientProvider.get(runningInstance.region);
262-
263-
const req = new SetTimeoutRequest();
264-
req.setId(runningInstance.id);
265-
req.setDuration(validatedDuration);
266-
await client.setTimeout(ctx, req);
267-
268-
return {
269-
resetTimeoutOnWorkspaces: [workspace.id],
270-
humanReadableDuration: this.goDurationToHumanReadable(validatedDuration),
271-
};
272-
}
273-
274-
public async getWorkspaceTimeout(ctx: TraceContext, workspaceId: string): Promise<GetWorkspaceTimeoutResult> {
275-
traceAPIParams(ctx, { workspaceId });
276-
traceWI(ctx, { workspaceId });
277-
278-
const user = this.checkUser("getWorkspaceTimeout");
279-
280-
const canChange = await this.entitlementService.maySetTimeout(user, new Date());
281-
282-
const workspace = await this.internalGetWorkspace(workspaceId, this.workspaceDb.trace(ctx));
283-
const runningInstance = await this.workspaceDb.trace(ctx).findRunningInstance(workspaceId);
284-
if (!runningInstance) {
285-
log.warn({ userId: user.id, workspaceId }, "Can only get keep-alive for running workspaces");
286-
const duration = WORKSPACE_TIMEOUT_DEFAULT_SHORT;
287-
return { duration, canChange, humanReadableDuration: this.goDurationToHumanReadable(duration) };
288-
}
289-
await this.guardAccess({ kind: "workspaceInstance", subject: runningInstance, workspace: workspace }, "get");
290-
291-
const req = new DescribeWorkspaceRequest();
292-
req.setId(runningInstance.id);
293-
294-
const client = await this.workspaceManagerClientProvider.get(runningInstance.region);
295-
const desc = await client.describeWorkspace(ctx, req);
296-
const duration = desc.getStatus()!.getSpec()!.getTimeout();
297-
298-
return { duration, canChange, humanReadableDuration: this.goDurationToHumanReadable(duration) };
299-
}
300-
301152
public async isPrebuildDone(ctx: TraceContext, pwsId: string): Promise<boolean> {
302153
traceAPIParams(ctx, { pwsId });
303154

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 124 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import {
7777
UserSSHPublicKeyValue,
7878
PrebuildEvent,
7979
RoleOrPermission,
80+
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
8081
} from "@gitpod/gitpod-protocol";
8182
import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositories-protocol";
8283
import {
@@ -117,6 +118,7 @@ import {
117118
MarkActiveRequest,
118119
PortSpec,
119120
PortVisibility as ProtoPortVisibility,
121+
SetTimeoutRequest,
120122
StopWorkspacePolicy,
121123
TakeSnapshotRequest,
122124
UpdateSSHKeyRequest,
@@ -164,7 +166,7 @@ import { InstallationAdminTelemetryDataProvider } from "../installation-admin/te
164166
import { ListUsageRequest, ListUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
165167
import { VerificationService } from "../auth/verification-service";
166168
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
167-
import { EntitlementService } from "../billing/entitlement-service";
169+
import { EntitlementService, MayStartWorkspaceResult } from "../billing/entitlement-service";
168170
import { formatPhoneNumber } from "../user/phone-numbers";
169171
import { IDEService } from "../ide-service";
170172
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
@@ -1388,18 +1390,41 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
13881390
return result;
13891391
}
13901392

1391-
/**
1392-
* Extension point for implementing entitlement checks. Throws a ResponseError if not eligible.
1393-
* @param ctx
1394-
* @param user
1395-
* @param runningInstances
1396-
*/
13971393
protected async mayStartWorkspace(
13981394
ctx: TraceContext,
13991395
user: User,
14001396
organizationId: string | undefined,
14011397
runningInstances: Promise<WorkspaceInstance[]>,
1402-
): Promise<void> {}
1398+
): Promise<void> {
1399+
let result: MayStartWorkspaceResult = {};
1400+
try {
1401+
result = await this.entitlementService.mayStartWorkspace(
1402+
user,
1403+
organizationId,
1404+
new Date(),
1405+
runningInstances,
1406+
);
1407+
TraceContext.addNestedTags(ctx, { mayStartWorkspace: { result } });
1408+
} catch (err) {
1409+
log.error({ userId: user.id }, "EntitlementSerivce.mayStartWorkspace error", err);
1410+
TraceContext.setError(ctx, err);
1411+
return; // we don't want to block workspace starts because of internal errors
1412+
}
1413+
if (!!result.needsVerification) {
1414+
throw new ResponseError(ErrorCodes.NEEDS_VERIFICATION, `Please verify your account.`);
1415+
}
1416+
if (!!result.usageLimitReachedOnCostCenter) {
1417+
throw new ResponseError(ErrorCodes.PAYMENT_SPENDING_LIMIT_REACHED, "Increase usage limit and try again.", {
1418+
attributionId: result.usageLimitReachedOnCostCenter,
1419+
});
1420+
}
1421+
if (!!result.hitParallelWorkspaceLimit) {
1422+
throw new ResponseError(
1423+
ErrorCodes.TOO_MANY_RUNNING_WORKSPACES,
1424+
`You cannot run more than ${result.hitParallelWorkspaceLimit.max} workspaces at the same time. Please stop a workspace before starting another one.`,
1425+
);
1426+
}
1427+
}
14031428

14041429
public async getFeaturedRepositories(ctx: TraceContext): Promise<WhitelistedRepository[]> {
14051430
const user = this.checkAndBlockUser("getFeaturedRepositories");
@@ -1559,17 +1584,100 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
15591584
workspaceId: string,
15601585
duration: WorkspaceTimeoutDuration,
15611586
): Promise<SetWorkspaceTimeoutResult> {
1562-
throw new ResponseError(
1563-
ErrorCodes.EE_FEATURE,
1564-
`Custom workspace timeout is implemented in Gitpod's Enterprise Edition`,
1565-
);
1587+
traceAPIParams(ctx, { workspaceId, duration });
1588+
traceWI(ctx, { workspaceId });
1589+
1590+
const user = this.checkUser("setWorkspaceTimeout");
1591+
1592+
if (!(await this.entitlementService.maySetTimeout(user, new Date()))) {
1593+
throw new ResponseError(ErrorCodes.PLAN_PROFESSIONAL_REQUIRED, "Plan upgrade is required");
1594+
}
1595+
1596+
let validatedDuration;
1597+
try {
1598+
validatedDuration = WorkspaceTimeoutDuration.validate(duration);
1599+
} catch (err) {
1600+
throw new ResponseError(ErrorCodes.INVALID_VALUE, "Invalid duration : " + err.message);
1601+
}
1602+
1603+
const workspace = await this.internalGetWorkspace(workspaceId, this.workspaceDb.trace(ctx));
1604+
const runningInstances = await this.workspaceDb.trace(ctx).findRegularRunningInstances(user.id);
1605+
const runningInstance = runningInstances.find((i) => i.workspaceId === workspaceId);
1606+
if (!runningInstance) {
1607+
throw new ResponseError(ErrorCodes.NOT_FOUND, "Can only set keep-alive for running workspaces");
1608+
}
1609+
await this.guardAccess({ kind: "workspaceInstance", subject: runningInstance, workspace: workspace }, "update");
1610+
1611+
const client = await this.workspaceManagerClientProvider.get(runningInstance.region);
1612+
1613+
const req = new SetTimeoutRequest();
1614+
req.setId(runningInstance.id);
1615+
req.setDuration(validatedDuration);
1616+
await client.setTimeout(ctx, req);
1617+
1618+
return {
1619+
resetTimeoutOnWorkspaces: [workspace.id],
1620+
humanReadableDuration: this.goDurationToHumanReadable(validatedDuration),
1621+
};
15661622
}
15671623

15681624
public async getWorkspaceTimeout(ctx: TraceContext, workspaceId: string): Promise<GetWorkspaceTimeoutResult> {
1569-
throw new ResponseError(
1570-
ErrorCodes.EE_FEATURE,
1571-
`Custom workspace timeout is implemented in Gitpod's Enterprise Edition`,
1572-
);
1625+
traceAPIParams(ctx, { workspaceId });
1626+
traceWI(ctx, { workspaceId });
1627+
1628+
const user = this.checkUser("getWorkspaceTimeout");
1629+
1630+
const canChange = await this.entitlementService.maySetTimeout(user, new Date());
1631+
1632+
const workspace = await this.internalGetWorkspace(workspaceId, this.workspaceDb.trace(ctx));
1633+
const runningInstance = await this.workspaceDb.trace(ctx).findRunningInstance(workspaceId);
1634+
if (!runningInstance) {
1635+
log.warn({ userId: user.id, workspaceId }, "Can only get keep-alive for running workspaces");
1636+
const duration = WORKSPACE_TIMEOUT_DEFAULT_SHORT;
1637+
return { duration, canChange, humanReadableDuration: this.goDurationToHumanReadable(duration) };
1638+
}
1639+
await this.guardAccess({ kind: "workspaceInstance", subject: runningInstance, workspace: workspace }, "get");
1640+
1641+
const req = new DescribeWorkspaceRequest();
1642+
req.setId(runningInstance.id);
1643+
1644+
const client = await this.workspaceManagerClientProvider.get(runningInstance.region);
1645+
const desc = await client.describeWorkspace(ctx, req);
1646+
const duration = desc.getStatus()!.getSpec()!.getTimeout();
1647+
1648+
return { duration, canChange, humanReadableDuration: this.goDurationToHumanReadable(duration) };
1649+
}
1650+
1651+
goDurationToHumanReadable(goDuration: string): string {
1652+
const [, value, unit] = goDuration.match(/^(\d+)([mh])$/)!;
1653+
let duration = parseInt(value);
1654+
1655+
switch (unit) {
1656+
case "m":
1657+
duration *= 60;
1658+
break;
1659+
case "h":
1660+
duration *= 60 * 60;
1661+
break;
1662+
}
1663+
1664+
const hours = Math.floor(duration / 3600);
1665+
duration %= 3600;
1666+
const minutes = Math.floor(duration / 60);
1667+
duration %= 60;
1668+
1669+
let result = "";
1670+
if (hours) {
1671+
result += `${hours} hour${hours === 1 ? "" : "s"}`;
1672+
if (minutes) {
1673+
result += " and ";
1674+
}
1675+
}
1676+
if (minutes) {
1677+
result += `${minutes} minute${minutes === 1 ? "" : "s"}`;
1678+
}
1679+
1680+
return result;
15731681
}
15741682

15751683
public async getOpenPorts(ctx: TraceContext, workspaceId: string): Promise<WorkspaceInstancePort[]> {

0 commit comments

Comments
 (0)