Skip to content

Commit 8cef174

Browse files
authored
[server] Move remaining trivial RPCs away from EE (#17346)
* [server] Move snapshot service out of EE * Fix * retest * [server] Move remaining trivial RPC away from EE
1 parent eb2cead commit 8cef174

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

+2-151
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 { PrebuildManager } from "../prebuilds/prebuild-manager";
@@ -61,7 +51,7 @@ import { ClientMetadata, traceClientMetadata } from "../../../src/websocket/webs
6151
import { BitbucketAppSupport } from "../bitbucket/bitbucket-app-support";
6252
import { URL } from "url";
6353
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
64-
import { EntitlementService, MayStartWorkspaceResult } from "../../../src/billing/entitlement-service";
54+
import { EntitlementService } from "../../../src/billing/entitlement-service";
6555
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
6656
import { BillingModes } from "../billing/billing-mode";
6757
import { UsageServiceDefinition } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
@@ -154,145 +144,6 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
154144
return allProjects;
155145
}
156146

157-
protected async mayStartWorkspace(
158-
ctx: TraceContext,
159-
user: User,
160-
organizationId: string | undefined,
161-
runningInstances: Promise<WorkspaceInstance[]>,
162-
): Promise<void> {
163-
await super.mayStartWorkspace(ctx, user, organizationId, runningInstances);
164-
165-
let result: MayStartWorkspaceResult = {};
166-
try {
167-
result = await this.entitlementService.mayStartWorkspace(
168-
user,
169-
organizationId,
170-
new Date(),
171-
runningInstances,
172-
);
173-
TraceContext.addNestedTags(ctx, { mayStartWorkspace: { result } });
174-
} catch (err) {
175-
log.error({ userId: user.id }, "EntitlementSerivce.mayStartWorkspace error", err);
176-
TraceContext.setError(ctx, err);
177-
return; // we don't want to block workspace starts because of internal errors
178-
}
179-
if (!!result.needsVerification) {
180-
throw new ResponseError(ErrorCodes.NEEDS_VERIFICATION, `Please verify your account.`);
181-
}
182-
if (!!result.usageLimitReachedOnCostCenter) {
183-
throw new ResponseError(ErrorCodes.PAYMENT_SPENDING_LIMIT_REACHED, "Increase usage limit and try again.", {
184-
attributionId: result.usageLimitReachedOnCostCenter,
185-
});
186-
}
187-
if (!!result.hitParallelWorkspaceLimit) {
188-
throw new ResponseError(
189-
ErrorCodes.TOO_MANY_RUNNING_WORKSPACES,
190-
`You cannot run more than ${result.hitParallelWorkspaceLimit.max} workspaces at the same time. Please stop a workspace before starting another one.`,
191-
);
192-
}
193-
}
194-
195-
goDurationToHumanReadable(goDuration: string): string {
196-
const [, value, unit] = goDuration.match(/^(\d+)([mh])$/)!;
197-
let duration = parseInt(value);
198-
199-
switch (unit) {
200-
case "m":
201-
duration *= 60;
202-
break;
203-
case "h":
204-
duration *= 60 * 60;
205-
break;
206-
}
207-
208-
const hours = Math.floor(duration / 3600);
209-
duration %= 3600;
210-
const minutes = Math.floor(duration / 60);
211-
duration %= 60;
212-
213-
let result = "";
214-
if (hours) {
215-
result += `${hours} hour${hours === 1 ? "" : "s"}`;
216-
if (minutes) {
217-
result += " and ";
218-
}
219-
}
220-
if (minutes) {
221-
result += `${minutes} minute${minutes === 1 ? "" : "s"}`;
222-
}
223-
224-
return result;
225-
}
226-
227-
public async setWorkspaceTimeout(
228-
ctx: TraceContext,
229-
workspaceId: string,
230-
duration: WorkspaceTimeoutDuration,
231-
): Promise<SetWorkspaceTimeoutResult> {
232-
traceAPIParams(ctx, { workspaceId, duration });
233-
traceWI(ctx, { workspaceId });
234-
235-
const user = this.checkUser("setWorkspaceTimeout");
236-
237-
if (!(await this.entitlementService.maySetTimeout(user, new Date()))) {
238-
throw new ResponseError(ErrorCodes.PLAN_PROFESSIONAL_REQUIRED, "Plan upgrade is required");
239-
}
240-
241-
let validatedDuration;
242-
try {
243-
validatedDuration = WorkspaceTimeoutDuration.validate(duration);
244-
} catch (err) {
245-
throw new ResponseError(ErrorCodes.INVALID_VALUE, "Invalid duration : " + err.message);
246-
}
247-
248-
const workspace = await this.internalGetWorkspace(workspaceId, this.workspaceDb.trace(ctx));
249-
const runningInstances = await this.workspaceDb.trace(ctx).findRegularRunningInstances(user.id);
250-
const runningInstance = runningInstances.find((i) => i.workspaceId === workspaceId);
251-
if (!runningInstance) {
252-
throw new ResponseError(ErrorCodes.NOT_FOUND, "Can only set keep-alive for running workspaces");
253-
}
254-
await this.guardAccess({ kind: "workspaceInstance", subject: runningInstance, workspace: workspace }, "update");
255-
256-
const client = await this.workspaceManagerClientProvider.get(runningInstance.region);
257-
258-
const req = new SetTimeoutRequest();
259-
req.setId(runningInstance.id);
260-
req.setDuration(validatedDuration);
261-
await client.setTimeout(ctx, req);
262-
263-
return {
264-
resetTimeoutOnWorkspaces: [workspace.id],
265-
humanReadableDuration: this.goDurationToHumanReadable(validatedDuration),
266-
};
267-
}
268-
269-
public async getWorkspaceTimeout(ctx: TraceContext, workspaceId: string): Promise<GetWorkspaceTimeoutResult> {
270-
traceAPIParams(ctx, { workspaceId });
271-
traceWI(ctx, { workspaceId });
272-
273-
const user = this.checkUser("getWorkspaceTimeout");
274-
275-
const canChange = await this.entitlementService.maySetTimeout(user, new Date());
276-
277-
const workspace = await this.internalGetWorkspace(workspaceId, this.workspaceDb.trace(ctx));
278-
const runningInstance = await this.workspaceDb.trace(ctx).findRunningInstance(workspaceId);
279-
if (!runningInstance) {
280-
log.warn({ userId: user.id, workspaceId }, "Can only get keep-alive for running workspaces");
281-
const duration = WORKSPACE_TIMEOUT_DEFAULT_SHORT;
282-
return { duration, canChange, humanReadableDuration: this.goDurationToHumanReadable(duration) };
283-
}
284-
await this.guardAccess({ kind: "workspaceInstance", subject: runningInstance, workspace: workspace }, "get");
285-
286-
const req = new DescribeWorkspaceRequest();
287-
req.setId(runningInstance.id);
288-
289-
const client = await this.workspaceManagerClientProvider.get(runningInstance.region);
290-
const desc = await client.describeWorkspace(ctx, req);
291-
const duration = desc.getStatus()!.getSpec()!.getTimeout();
292-
293-
return { duration, canChange, humanReadableDuration: this.goDurationToHumanReadable(duration) };
294-
}
295-
296147
public async isPrebuildDone(ctx: TraceContext, pwsId: string): Promise<boolean> {
297148
traceAPIParams(ctx, { pwsId });
298149

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

+124-16
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 {
@@ -112,6 +113,7 @@ import {
112113
MarkActiveRequest,
113114
PortSpec,
114115
PortVisibility as ProtoPortVisibility,
116+
SetTimeoutRequest,
115117
StopWorkspacePolicy,
116118
TakeSnapshotRequest,
117119
UpdateSSHKeyRequest,
@@ -159,7 +161,7 @@ import { InstallationAdminTelemetryDataProvider } from "../installation-admin/te
159161
import { ListUsageRequest, ListUsageResponse } from "@gitpod/gitpod-protocol/lib/usage";
160162
import { VerificationService } from "../auth/verification-service";
161163
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
162-
import { EntitlementService } from "../billing/entitlement-service";
164+
import { EntitlementService, MayStartWorkspaceResult } from "../billing/entitlement-service";
163165
import { formatPhoneNumber } from "../user/phone-numbers";
164166
import { IDEService } from "../ide-service";
165167
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
@@ -1383,18 +1385,41 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
13831385
return result;
13841386
}
13851387

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

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

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

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

0 commit comments

Comments
 (0)