Skip to content
Merged
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
8 changes: 5 additions & 3 deletions apps/supervisor/src/services/computeSnapshotService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,13 @@ export class ComputeSnapshotService {

/** Handle the callback from the gateway after a snapshot completes or fails. */
async handleCallback(body: SnapshotCallbackPayload) {
const snapshotId = body.status === "completed" ? body.snapshot_id : undefined;

this.logger.debug("Snapshot callback", {
snapshotId: body.snapshot_id,
snapshotId,
instanceId: body.instance_id,
status: body.status,
error: body.error,
error: body.status === "failed" ? body.error : undefined,
metadata: body.metadata,
durationMs: body.duration_ms,
});
Expand All @@ -97,7 +99,7 @@ export class ComputeSnapshotService {
return { ok: false as const, status: 400 };
}

this.#emitSnapshotSpan(runId, body.duration_ms, body.snapshot_id);
this.#emitSnapshotSpan(runId, body.duration_ms, snapshotId);

if (body.status === "completed") {
const result = await this.workerClient.submitSuspendCompletion({
Expand Down
13 changes: 10 additions & 3 deletions apps/webapp/app/presenters/v3/RegionsPresenter.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type Project } from "~/models/project.server";
import { type User } from "~/models/user.server";
import { FEATURE_FLAG } from "~/v3/featureFlags";
import { makeFlag } from "~/v3/featureFlags.server";
import { defaultVisibilityFilter, resolveComputeAccess } from "~/v3/regionAccess.server";
import { BasePresenter } from "./basePresenter.server";
import { getCurrentPlan } from "~/services/platform.v3.server";

Expand Down Expand Up @@ -32,6 +33,9 @@ export class RegionsPresenter extends BasePresenter {
organizationId: true,
defaultWorkerGroupId: true,
allowedWorkerQueues: true,
organization: {
select: { featureFlags: true },
},
},
where: {
slug: projectSlug,
Expand All @@ -58,6 +62,11 @@ export class RegionsPresenter extends BasePresenter {
throw new Error("Default worker instance group not found");
}

const hasComputeAccess = await resolveComputeAccess(
this._replica,
project.organization.featureFlags
);

const visibleRegions = await this._replica.workerInstanceGroup.findMany({
select: {
id: true,
Expand All @@ -75,9 +84,7 @@ export class RegionsPresenter extends BasePresenter {
? {
masterQueue: { in: project.allowedWorkerQueues },
}
: {
hidden: false,
},
: defaultVisibilityFilter(hasComputeAccess),
orderBy: {
name: "asc",
},
Expand Down
50 changes: 50 additions & 0 deletions apps/webapp/app/v3/regionAccess.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { type Prisma, type WorkloadType } from "@trigger.dev/database";
import { type PrismaClientOrTransaction } from "~/db.server";
import { FEATURE_FLAG } from "./featureFlags";
import { makeFlag } from "./featureFlags.server";

/**
* Resolves whether an org has compute access based on feature flags.
*/
export async function resolveComputeAccess(
prisma: PrismaClientOrTransaction,
orgFeatureFlags: unknown
): Promise<boolean> {
const flag = makeFlag(prisma);
return flag({
key: FEATURE_FLAG.hasComputeAccess,
defaultValue: false,
overrides: (orgFeatureFlags as Record<string, unknown>) ?? {},
});
}

/**
* Builds a visibility filter for non-admin, non-allowlisted users.
* Without compute access, MICROVM regions are excluded entirely.
* With compute access, hidden flag works normally (existing behavior).
*/
export function defaultVisibilityFilter(
hasComputeAccess: boolean
): Prisma.WorkerInstanceGroupWhereInput {
if (hasComputeAccess) {
return { hidden: false };
}

return { hidden: false, workloadType: { not: "MICROVM" } };
}

/**
* Whether a region is accessible given compute access.
* MICROVM regions require compute access; all other types pass through.
*/
export function isComputeRegionAccessible(
region: { workloadType: WorkloadType },
hasComputeAccess: boolean
): boolean {
if (region.workloadType !== "MICROVM") {
return true;
}

// Allow access to any MICROVM region if the org has compute access
return hasComputeAccess;
}
20 changes: 8 additions & 12 deletions apps/webapp/app/v3/services/computeTemplateCreation.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import { machinePresetFromName } from "~/v3/machinePresets.server";
import { env } from "~/env.server";
import { logger } from "~/services/logger.server";
import type { PrismaClientOrTransaction } from "~/db.server";
import { FEATURE_FLAG } from "~/v3/featureFlags";
import { makeFlag } from "~/v3/featureFlags.server";
import type { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { ServiceValidationError } from "./baseService.server";
import { FailDeploymentService } from "./failDeployment.server";
import { resolveComputeAccess } from "../regionAccess.server";

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

Expand Down Expand Up @@ -101,9 +100,7 @@ export class ComputeTemplateCreationService {
},
});

throw new ServiceValidationError(
`Compute template creation failed: ${result.error}`
);
throw new ServiceValidationError(`Compute template creation failed: ${result.error}`);
}

logger.info("Compute template created", {
Expand Down Expand Up @@ -132,16 +129,15 @@ export class ComputeTemplateCreationService {
},
});

if (project?.defaultWorkerGroup?.workloadType === "MICROVM") {
if (!project) {
return "skip";
}

if (project.defaultWorkerGroup?.workloadType === "MICROVM") {
return "required";
}

const flag = makeFlag(prisma);
const hasComputeAccess = await flag({
key: FEATURE_FLAG.hasComputeAccess,
defaultValue: false,
overrides: (project?.organization?.featureFlags as Record<string, unknown>) ?? {},
});
const hasComputeAccess = await resolveComputeAccess(prisma, project.organization.featureFlags);

if (hasComputeAccess) {
return "shadow";
Expand Down
21 changes: 19 additions & 2 deletions apps/webapp/app/v3/services/setDefaultRegion.server.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isComputeRegionAccessible, resolveComputeAccess } from "~/v3/regionAccess.server";
import { BaseService, ServiceValidationError } from "./baseService.server";

export class SetDefaultRegionService extends BaseService {
Expand All @@ -24,6 +25,9 @@ export class SetDefaultRegionService extends BaseService {
where: {
id: projectId,
},
include: {
organization: { select: { featureFlags: true } },
},
});

if (!project) {
Expand All @@ -36,8 +40,21 @@ export class SetDefaultRegionService extends BaseService {
if (!project.allowedWorkerQueues.includes(workerGroup.masterQueue)) {
throw new ServiceValidationError("You're not allowed to set this region as default");
}
} else if (workerGroup.hidden) {
throw new ServiceValidationError("This region is not available to you");
} else {
if (workerGroup.hidden) {
throw new ServiceValidationError("This region is not available to you");
}

if (workerGroup.workloadType === "MICROVM") {
const hasComputeAccess = await resolveComputeAccess(
this._prisma,
project.organization.featureFlags
);

if (!isComputeRegionAccessible(workerGroup, hasComputeAccess)) {
throw new ServiceValidationError("This region requires compute access");
}
}
}
}

Expand Down
13 changes: 13 additions & 0 deletions apps/webapp/app/v3/services/worker/workerGroupService.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { WorkerGroupTokenService } from "./workerGroupTokenService.server";
import { logger } from "~/services/logger.server";
import { FEATURE_FLAG } from "~/v3/featureFlags";
import { makeFlag, makeSetFlag } from "~/v3/featureFlags.server";
import { isComputeRegionAccessible, resolveComputeAccess } from "~/v3/regionAccess.server";

export class WorkerGroupService extends WithRunEngine {
private readonly defaultNamePrefix = "worker_group";
Expand Down Expand Up @@ -207,6 +208,7 @@ export class WorkerGroupService extends WithRunEngine {
},
include: {
defaultWorkerGroup: true,
organization: { select: { featureFlags: true } },
},
});

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

if (workerGroup.workloadType === "MICROVM") {
const hasComputeAccess = await resolveComputeAccess(
this._prisma,
project.organization.featureFlags
);

if (!isComputeRegionAccessible(workerGroup, hasComputeAccess)) {
throw new Error(`The region you specified isn't available to you ("${regionOverride}").`);
}
}

return workerGroup;
}

Expand Down
24 changes: 16 additions & 8 deletions internal-packages/compute/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,20 @@ export const SnapshotRestoreRequestSchema = z.object({
});
export type SnapshotRestoreRequest = z.infer<typeof SnapshotRestoreRequestSchema>;

export const SnapshotCallbackPayloadSchema = z.object({
snapshot_id: z.string(),
instance_id: z.string(),
status: z.enum(["completed", "failed"]),
error: z.string().optional(),
metadata: z.record(z.string()).optional(),
duration_ms: z.number().optional(),
});
export const SnapshotCallbackPayloadSchema = z.discriminatedUnion("status", [
z.object({
status: z.literal("completed"),
snapshot_id: z.string(),
instance_id: z.string(),
metadata: z.record(z.string()).optional(),
duration_ms: z.number().optional(),
}),
z.object({
status: z.literal("failed"),
instance_id: z.string(),
error: z.string().optional(),
metadata: z.record(z.string()).optional(),
duration_ms: z.number().optional(),
}),
]);
export type SnapshotCallbackPayload = z.infer<typeof SnapshotCallbackPayloadSchema>;
Loading