Skip to content

Commit 13d02be

Browse files
1stvampTrigger.dev RepoOps
authored andcommitted
feat(webapp): prebuild the microVM template at deploy finalisation
Optionally prebuild a deployment's microVM template during deploy finalisation when a template builder is configured. The call runs in the background by default, can be awaited with a timeout behind a feature flag, and never fails a deploy. A successful build records the template key on the deployment. Mono-RevId: 1bee982a567d873ab0171eb27ca4742e491f72ea
1 parent 434b7f0 commit 13d02be

7 files changed

Lines changed: 1129 additions & 4 deletions

File tree

‎apps/webapp/app/env.server.ts‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from "zod";
2+
import { MachinePresetName } from "@trigger.dev/core/v3";
23
import {
34
parseNaturalLanguageDurationInMs,
45
webhookDeliveryRetentionClass,
@@ -31,6 +32,35 @@ const parseDeployBaseImagesEnv = (
3132
return errors.length > 0 ? z.NEVER : images;
3233
};
3334

35+
// Parses a CSV of machine preset names (e.g. "small-1x,small-2x") into a
36+
// non-empty array of MachinePresetName.
37+
const parseMachinePresetCsv = (raw: string, ctx: z.RefinementCtx): MachinePresetName[] => {
38+
const names = raw
39+
.split(",")
40+
.map((s) => s.trim())
41+
.filter(Boolean);
42+
if (names.length === 0) {
43+
ctx.addIssue({
44+
code: z.ZodIssueCode.custom,
45+
message: "must list at least one machine preset",
46+
});
47+
return z.NEVER;
48+
}
49+
const out: MachinePresetName[] = [];
50+
for (const name of names) {
51+
const parsed = MachinePresetName.safeParse(name);
52+
if (!parsed.success) {
53+
ctx.addIssue({
54+
code: z.ZodIssueCode.custom,
55+
message: `unknown machine preset: "${name}"`,
56+
});
57+
return z.NEVER;
58+
}
59+
out.push(parsed.data);
60+
}
61+
return out;
62+
};
63+
3464
const GithubAppEnvSchema = z.preprocess(
3565
(val) => {
3666
const obj = val as any;
@@ -877,6 +907,20 @@ const EnvironmentSchema = z
877907
.optional()
878908
.transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_DEFAULT_REPOSITORY_POLICY),
879909

910+
// MicroVM template builder, called at deploy finalisation to prebuild the
911+
// deployment image's template. Unset URL skips it.
912+
TEMPLATE_BUILDER_URL: z.string().optional(),
913+
TEMPLATE_BUILDER_TOKEN: z.string().optional(),
914+
// How long wait mode holds finalisation for the builder before carrying on.
915+
TEMPLATE_BUILDER_TIMEOUT_MS: z.coerce.number().int().positive().default(60_000),
916+
// Machine presets whose shapes are sent with the template request.
917+
TEMPLATE_BUILDER_MACHINE_PRESETS: z
918+
.string()
919+
.default("small-1x")
920+
.transform(parseMachinePresetCsv),
921+
// Percentage of deploys from orgs without compute that still prebuild in shadow mode.
922+
TEMPLATE_BUILDER_SHADOW_ROLLOUT_PCT: z.coerce.number().min(0).max(100).default(0),
923+
880924
DEPLOY_IMAGE_PLATFORM: z.string().default("linux/amd64"),
881925
DEPLOY_BASE_IMAGES: z
882926
.string()

‎apps/webapp/app/v3/featureFlags.ts‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const FEATURE_FLAG = {
2626
computeMigrationEnabled: "computeMigrationEnabled",
2727
computeMigrationFreePercentage: "computeMigrationFreePercentage",
2828
computeMigrationPaidPercentage: "computeMigrationPaidPercentage",
29-
computeMigrationRequireTemplate: "computeMigrationRequireTemplate",
29+
computeTemplatePrebuildWait: "computeTemplatePrebuildWait",
3030
runOpsMintKind: "runOpsMintKind",
3131
// Grace-linger stamp carried alongside runOpsMintKind on flip. See mintFlipGrace.ts.
3232
runOpsMintKindPrev: "runOpsMintKindPrev",
@@ -117,9 +117,10 @@ export const FeatureFlagCatalog = {
117117
[FEATURE_FLAG.computeMigrationEnabled]: z.boolean(),
118118
[FEATURE_FLAG.computeMigrationFreePercentage]: z.coerce.number().int().min(0).max(100),
119119
[FEATURE_FLAG.computeMigrationPaidPercentage]: z.coerce.number().int().min(0).max(100),
120-
// When on, migrated orgs build their compute template in required mode at deploy
121-
// (fails the deploy on error) instead of shadow. Strict boolean (see above).
122-
[FEATURE_FLAG.computeMigrationRequireTemplate]: z.boolean(),
120+
// When on, deploy finalisation waits (up to a timeout) for the compute template
121+
// prebuild instead of firing it in the background. Never fails the deploy.
122+
// Global or per org. Strict boolean (see above).
123+
[FEATURE_FLAG.computeTemplatePrebuildWait]: z.boolean(),
123124
// Per-org run-ops-id mint cutover. Defaults to "cuid"; only honored when
124125
// RUN_OPS_MINT_ENABLED is on AND isSplitEnabled() is true.
125126
[FEATURE_FLAG.runOpsMintKind]: z.enum(["cuid", "runOpsId"]),

‎apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getEcrAuthToken, isEcrRegistry } from "../getDeploymentImageRef.server"
1616
import { tryCatch } from "@trigger.dev/core";
1717
import { getRegistryConfig, type RegistryConfig } from "../registryConfig.server";
1818
import { ecrImageExists } from "./verifyDeploymentImage.server";
19+
import { TemplatePrebuildService } from "./templatePrebuild.server";
1920

2021
export class FinalizeDeploymentV2Service extends BaseService {
2122
public async call(
@@ -94,6 +95,7 @@ export class FinalizeDeploymentV2Service extends BaseService {
9495
// self-hosted setup). Verify before promoting so we never mark a
9596
// deployment DEPLOYED when nothing was actually pushed.
9697
await this.#assertImagePullable(deployment, body);
98+
await this.#prebuildTemplate(deployment, id, body, writer);
9799

98100
return finalizeService.call(authenticatedEnv, id, body);
99101
}
@@ -165,10 +167,36 @@ export class FinalizeDeploymentV2Service extends BaseService {
165167

166168
// Belt and suspenders: confirm the push actually landed before promoting.
167169
await this.#assertImagePullable(deployment, body);
170+
await this.#prebuildTemplate(deployment, id, body, writer);
168171

169172
return finalizeService.call(authenticatedEnv, id, body);
170173
}
171174

175+
// Never throws, so a template problem cannot fail the deploy.
176+
async #prebuildTemplate(
177+
deployment: {
178+
id: string;
179+
imageReference: string | null;
180+
worker: { project: { id: string } } | null;
181+
},
182+
deploymentFriendlyId: string,
183+
body: FinalizeDeploymentRequestBody,
184+
writer?: WritableStreamDefaultWriter
185+
): Promise<void> {
186+
if (!deployment.imageReference || !deployment.worker) {
187+
return;
188+
}
189+
190+
await new TemplatePrebuildService(this._prisma).prebuild({
191+
projectId: deployment.worker.project.id,
192+
deploymentId: deployment.id,
193+
deploymentFriendlyId,
194+
imageReference: deployment.imageReference,
195+
imageDigest: body.imageDigest,
196+
writer,
197+
});
198+
}
199+
172200
async #assertImagePullable(
173201
deployment: { imageReference: string | null; type: string | null },
174202
body: FinalizeDeploymentRequestBody

0 commit comments

Comments
 (0)