Skip to content

Commit f87b52c

Browse files
committed
feat(webapp): gate microvm regions behind compute access feature flag
1 parent bd41bb2 commit f87b52c

File tree

5 files changed

+100
-17
lines changed

5 files changed

+100
-17
lines changed

apps/webapp/app/presenters/v3/RegionsPresenter.server.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { type Project } from "~/models/project.server";
22
import { type User } from "~/models/user.server";
33
import { FEATURE_FLAG } from "~/v3/featureFlags";
44
import { makeFlag } from "~/v3/featureFlags.server";
5+
import { defaultVisibilityFilter, resolveComputeAccess } from "~/v3/regionAccess.server";
56
import { BasePresenter } from "./basePresenter.server";
67
import { getCurrentPlan } from "~/services/platform.v3.server";
78

@@ -32,6 +33,9 @@ export class RegionsPresenter extends BasePresenter {
3233
organizationId: true,
3334
defaultWorkerGroupId: true,
3435
allowedWorkerQueues: true,
36+
organization: {
37+
select: { featureFlags: true },
38+
},
3539
},
3640
where: {
3741
slug: projectSlug,
@@ -58,6 +62,11 @@ export class RegionsPresenter extends BasePresenter {
5862
throw new Error("Default worker instance group not found");
5963
}
6064

65+
const hasComputeAccess = await resolveComputeAccess(
66+
this._replica,
67+
project.organization.featureFlags
68+
);
69+
6170
const visibleRegions = await this._replica.workerInstanceGroup.findMany({
6271
select: {
6372
id: true,
@@ -75,9 +84,7 @@ export class RegionsPresenter extends BasePresenter {
7584
? {
7685
masterQueue: { in: project.allowedWorkerQueues },
7786
}
78-
: {
79-
hidden: false,
80-
},
87+
: defaultVisibilityFilter(hasComputeAccess),
8188
orderBy: {
8289
name: "asc",
8390
},
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { type Prisma, type WorkloadType } from "@trigger.dev/database";
2+
import { type PrismaClientOrTransaction } from "~/db.server";
3+
import { FEATURE_FLAG } from "./featureFlags";
4+
import { makeFlag } from "./featureFlags.server";
5+
6+
/**
7+
* Resolves whether an org has compute access based on feature flags.
8+
*/
9+
export async function resolveComputeAccess(
10+
prisma: PrismaClientOrTransaction,
11+
orgFeatureFlags: unknown
12+
): Promise<boolean> {
13+
const flag = makeFlag(prisma);
14+
return flag({
15+
key: FEATURE_FLAG.hasComputeAccess,
16+
defaultValue: false,
17+
overrides: (orgFeatureFlags as Record<string, unknown>) ?? {},
18+
});
19+
}
20+
21+
/**
22+
* Builds a visibility filter for non-admin, non-allowlisted users.
23+
* Without compute access, MICROVM regions are excluded entirely.
24+
* With compute access, hidden flag works normally (existing behavior).
25+
*/
26+
export function defaultVisibilityFilter(
27+
hasComputeAccess: boolean
28+
): Prisma.WorkerInstanceGroupWhereInput {
29+
if (hasComputeAccess) {
30+
return { hidden: false };
31+
}
32+
33+
return { hidden: false, workloadType: { not: "MICROVM" } };
34+
}
35+
36+
/**
37+
* Whether a region is accessible given compute access.
38+
* MICROVM regions require compute access; all other types pass through.
39+
*/
40+
export function isComputeRegionAccessible(
41+
region: { workloadType: WorkloadType },
42+
hasComputeAccess: boolean
43+
): boolean {
44+
if (region.workloadType !== "MICROVM") {
45+
return true;
46+
}
47+
48+
// Allow access to any MICROVM region if the org has compute access
49+
return hasComputeAccess;
50+
}

apps/webapp/app/v3/services/computeTemplateCreation.server.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import { machinePresetFromName } from "~/v3/machinePresets.server";
33
import { env } from "~/env.server";
44
import { logger } from "~/services/logger.server";
55
import type { PrismaClientOrTransaction } from "~/db.server";
6-
import { FEATURE_FLAG } from "~/v3/featureFlags";
7-
import { makeFlag } from "~/v3/featureFlags.server";
86
import type { AuthenticatedEnvironment } from "~/services/apiAuth.server";
97
import { ServiceValidationError } from "./baseService.server";
108
import { FailDeploymentService } from "./failDeployment.server";
9+
import { resolveComputeAccess } from "../regionAccess.server";
1110

1211
type TemplateCreationMode = "required" | "shadow" | "skip";
1312

@@ -101,9 +100,7 @@ export class ComputeTemplateCreationService {
101100
},
102101
});
103102

104-
throw new ServiceValidationError(
105-
`Compute template creation failed: ${result.error}`
106-
);
103+
throw new ServiceValidationError(`Compute template creation failed: ${result.error}`);
107104
}
108105

109106
logger.info("Compute template created", {
@@ -132,16 +129,15 @@ export class ComputeTemplateCreationService {
132129
},
133130
});
134131

135-
if (project?.defaultWorkerGroup?.workloadType === "MICROVM") {
132+
if (!project) {
133+
return "skip";
134+
}
135+
136+
if (project.defaultWorkerGroup?.workloadType === "MICROVM") {
136137
return "required";
137138
}
138139

139-
const flag = makeFlag(prisma);
140-
const hasComputeAccess = await flag({
141-
key: FEATURE_FLAG.hasComputeAccess,
142-
defaultValue: false,
143-
overrides: (project?.organization?.featureFlags as Record<string, unknown>) ?? {},
144-
});
140+
const hasComputeAccess = await resolveComputeAccess(prisma, project.organization.featureFlags);
145141

146142
if (hasComputeAccess) {
147143
return "shadow";

apps/webapp/app/v3/services/setDefaultRegion.server.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isComputeRegionAccessible, resolveComputeAccess } from "~/v3/regionAccess.server";
12
import { BaseService, ServiceValidationError } from "./baseService.server";
23

34
export class SetDefaultRegionService extends BaseService {
@@ -24,6 +25,9 @@ export class SetDefaultRegionService extends BaseService {
2425
where: {
2526
id: projectId,
2627
},
28+
include: {
29+
organization: { select: { featureFlags: true } },
30+
},
2731
});
2832

2933
if (!project) {
@@ -36,8 +40,21 @@ export class SetDefaultRegionService extends BaseService {
3640
if (!project.allowedWorkerQueues.includes(workerGroup.masterQueue)) {
3741
throw new ServiceValidationError("You're not allowed to set this region as default");
3842
}
39-
} else if (workerGroup.hidden) {
40-
throw new ServiceValidationError("This region is not available to you");
43+
} else {
44+
if (workerGroup.hidden) {
45+
throw new ServiceValidationError("This region is not available to you");
46+
}
47+
48+
if (workerGroup.workloadType === "MICROVM") {
49+
const hasComputeAccess = await resolveComputeAccess(
50+
this._prisma,
51+
project.organization.featureFlags
52+
);
53+
54+
if (!isComputeRegionAccessible(workerGroup, hasComputeAccess)) {
55+
throw new ServiceValidationError("This region requires compute access");
56+
}
57+
}
4158
}
4259
}
4360

apps/webapp/app/v3/services/worker/workerGroupService.server.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { WorkerGroupTokenService } from "./workerGroupTokenService.server";
44
import { logger } from "~/services/logger.server";
55
import { FEATURE_FLAG } from "~/v3/featureFlags";
66
import { makeFlag, makeSetFlag } from "~/v3/featureFlags.server";
7+
import { isComputeRegionAccessible, resolveComputeAccess } from "~/v3/regionAccess.server";
78

89
export class WorkerGroupService extends WithRunEngine {
910
private readonly defaultNamePrefix = "worker_group";
@@ -207,6 +208,7 @@ export class WorkerGroupService extends WithRunEngine {
207208
},
208209
include: {
209210
defaultWorkerGroup: true,
211+
organization: { select: { featureFlags: true } },
210212
},
211213
});
212214

@@ -243,6 +245,17 @@ export class WorkerGroupService extends WithRunEngine {
243245
throw new Error(`The region you specified isn't available to you ("${regionOverride}").`);
244246
}
245247

248+
if (workerGroup.workloadType === "MICROVM") {
249+
const hasComputeAccess = await resolveComputeAccess(
250+
this._prisma,
251+
project.organization.featureFlags
252+
);
253+
254+
if (!isComputeRegionAccessible(workerGroup, hasComputeAccess)) {
255+
throw new Error(`The region you specified isn't available to you ("${regionOverride}").`);
256+
}
257+
}
258+
246259
return workerGroup;
247260
}
248261

0 commit comments

Comments
 (0)