Skip to content

Commit ad22de2

Browse files
committed
[server] Move remaining trivial RPC away from EE
1 parent 95317e9 commit ad22de2

File tree

2 files changed

+127
-171
lines changed

2 files changed

+127
-171
lines changed

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

Lines changed: 3 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,30 @@
55
*/
66

77
import { injectable, inject } from "inversify";
8-
import { GitpodServerImpl, traceAPIParams, traceWI, censor } from "../../../src/workspace/gitpod-server-impl";
8+
import { GitpodServerImpl, traceAPIParams, traceWI } from "../../../src/workspace/gitpod-server-impl";
99
import { TraceContext, TraceContextWithSpan } from "@gitpod/gitpod-protocol/lib/util/tracing";
1010
import {
1111
GitpodClient,
12-
AdminGetListRequest,
1312
User,
1413
Team,
15-
AdminGetListResult,
1614
Permission,
17-
AdminBlockUserRequest,
18-
GetWorkspaceTimeoutResult,
19-
WorkspaceTimeoutDuration,
20-
SetWorkspaceTimeoutResult,
2115
WorkspaceContext,
2216
WorkspaceCreationResult,
2317
PrebuiltWorkspaceContext,
2418
CommitContext,
2519
PrebuiltWorkspace,
26-
WorkspaceInstance,
2720
ProviderRepository,
2821
PrebuildWithStatus,
2922
CreateProjectParams,
3023
Project,
3124
StartPrebuildResult,
3225
ClientHeaderFields,
3326
FindPrebuildsParams,
34-
WORKSPACE_TIMEOUT_DEFAULT_SHORT,
3527
PrebuildEvent,
3628
OpenPrebuildContext,
3729
} from "@gitpod/gitpod-protocol";
3830
import { ResponseError } from "vscode-jsonrpc";
39-
import {
40-
AdmissionLevel,
41-
ControlAdmissionRequest,
42-
DescribeWorkspaceRequest,
43-
SetTimeoutRequest,
44-
} from "@gitpod/ws-manager/lib";
31+
import { AdmissionLevel, ControlAdmissionRequest } from "@gitpod/ws-manager/lib";
4532
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
4633
import { log, LogContext } from "@gitpod/gitpod-protocol/lib/util/logging";
4734
import { LicenseValidationResult } from "@gitpod/gitpod-protocol/lib/license-protocol";
@@ -65,7 +52,7 @@ import { ClientMetadata, traceClientMetadata } from "../../../src/websocket/webs
6552
import { BitbucketAppSupport } from "../bitbucket/bitbucket-app-support";
6653
import { URL } from "url";
6754
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
68-
import { EntitlementService, MayStartWorkspaceResult } from "../../../src/billing/entitlement-service";
55+
import { EntitlementService } from "../../../src/billing/entitlement-service";
6956
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
7057
import { BillingModes } from "../billing/billing-mode";
7158
import { UsageServiceDefinition } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
@@ -158,149 +145,10 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
158145
return allProjects;
159146
}
160147

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

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

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)