Skip to content

Commit cc94d12

Browse files
authored
feat: installing status for deployments (#2544)
* Add installing status to the deployment db schema * Replace the deployments /start endpoint with /progress * Show the installing status in the dashboard * Add installing status to the api schema and cli * Add changeset
1 parent 412e80f commit cc94d12

File tree

8 files changed

+102
-39
lines changed

8 files changed

+102
-39
lines changed

.changeset/kind-kids-teach.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"trigger.dev": patch
3+
"@trigger.dev/core": patch
4+
---
5+
6+
Added INSTALLING status to the deployment status enum.

apps/webapp/app/components/runs/v3/DeploymentStatus.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export function DeploymentStatusIcon({
5353
return (
5454
<RectangleStackIcon className={cn(deploymentStatusClassNameColor(status), className)} />
5555
);
56+
case "INSTALLING":
5657
case "BUILDING":
5758
case "DEPLOYING":
5859
return <Spinner className={cn(deploymentStatusClassNameColor(status), className)} />;
@@ -78,6 +79,7 @@ export function deploymentStatusClassNameColor(status: WorkerDeploymentStatus):
7879
switch (status) {
7980
case "PENDING":
8081
return "text-charcoal-500";
82+
case "INSTALLING":
8183
case "BUILDING":
8284
case "DEPLOYING":
8385
return "text-pending";
@@ -98,6 +100,8 @@ export function deploymentStatusTitle(status: WorkerDeploymentStatus, isBuilt: b
98100
switch (status) {
99101
case "PENDING":
100102
return "Queued…";
103+
case "INSTALLING":
104+
return "Installing…";
101105
case "BUILDING":
102106
return "Building…";
103107
case "DEPLOYING":
@@ -127,6 +131,7 @@ export function deploymentStatusTitle(status: WorkerDeploymentStatus, isBuilt: b
127131
// PENDING and CANCELED are not used so are ommited from the UI
128132
export const deploymentStatuses: WorkerDeploymentStatus[] = [
129133
"PENDING",
134+
"INSTALLING",
130135
"BUILDING",
131136
"DEPLOYING",
132137
"DEPLOYED",
@@ -138,6 +143,8 @@ export function deploymentStatusDescription(status: WorkerDeploymentStatus): str
138143
switch (status) {
139144
case "PENDING":
140145
return "The deployment is queued and waiting to be processed.";
146+
case "INSTALLING":
147+
return "The project dependencies are being installed.";
141148
case "BUILDING":
142149
return "The code is being built and prepared for deployment.";
143150
case "DEPLOYING":

apps/webapp/app/routes/api.v1.deployments.$deploymentId.start.ts renamed to apps/webapp/app/routes/api.v1.deployments.$deploymentId.progress.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
2-
import { StartDeploymentRequestBody } from "@trigger.dev/core/v3";
2+
import { ProgressDeploymentRequestBody } from "@trigger.dev/core/v3";
33
import { z } from "zod";
44
import { authenticateRequest } from "~/services/apiAuth.server";
55
import { logger } from "~/services/logger.server";
@@ -35,7 +35,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
3535
const { deploymentId } = parsedParams.data;
3636

3737
const rawBody = await request.json();
38-
const body = StartDeploymentRequestBody.safeParse(rawBody);
38+
const body = ProgressDeploymentRequestBody.safeParse(rawBody);
3939

4040
if (!body.success) {
4141
return json({ error: "Invalid request body", issues: body.error.issues }, { status: 400 });
@@ -44,7 +44,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
4444
const deploymentService = new DeploymentService();
4545

4646
return await deploymentService
47-
.startDeployment(authenticatedEnv, deploymentId, {
47+
.progressDeployment(authenticatedEnv, deploymentId, {
4848
contentHash: body.data.contentHash,
4949
git: body.data.gitMeta,
5050
runtime: body.data.runtime,
@@ -59,8 +59,11 @@ export async function action({ request, params }: ActionFunctionArgs) {
5959
return new Response(null, { status: 204 }); // ignore these errors for now
6060
case "deployment_not_found":
6161
return json({ error: "Deployment not found" }, { status: 404 });
62-
case "deployment_not_pending":
63-
return json({ error: "Deployment is not pending" }, { status: 409 });
62+
case "deployment_cannot_be_progressed":
63+
return json(
64+
{ error: "Deployment is not in a progressable state (PENDING or INSTALLING)" },
65+
{ status: 409 }
66+
);
6467
case "failed_to_create_remote_build":
6568
return json({ error: "Failed to create remote build" }, { status: 500 });
6669
case "other":

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

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@ import { env } from "~/env.server";
88
import { createRemoteImageBuild } from "../remoteImageBuilder.server";
99

1010
export class DeploymentService extends BaseService {
11-
public startDeployment(
11+
/**
12+
* Progresses a deployment from PENDING to INSTALLING and then to BUILDING.
13+
* Also extends the deployment timeout.
14+
*
15+
* When progressing to BUILDING, the remote Depot build is also created.
16+
*
17+
* Only acts when the current status allows. Not idempotent.
18+
*
19+
* @param authenticatedEnv The environment which the deployment belongs to.
20+
* @param friendlyId The friendly deployment ID.
21+
* @param updates Optional deployment details to persist.
22+
*/
23+
24+
public progressDeployment(
1225
authenticatedEnv: AuthenticatedEnvironment,
1326
friendlyId: string,
1427
updates: Partial<Pick<WorkerDeployment, "contentHash" | "runtime"> & { git: GitMeta }>
@@ -37,37 +50,26 @@ export class DeploymentService extends BaseService {
3750
});
3851

3952
const validateDeployment = (deployment: Pick<WorkerDeployment, "id" | "status">) => {
40-
if (deployment.status !== "PENDING") {
41-
logger.warn("Attempted starting deployment that is not in PENDING status", {
42-
deployment,
43-
});
44-
return errAsync({ type: "deployment_not_pending" as const });
53+
if (deployment.status !== "PENDING" && deployment.status !== "INSTALLING") {
54+
logger.warn(
55+
"Attempted progressing deployment that is not in PENDING or INSTALLING status",
56+
{
57+
deployment,
58+
}
59+
);
60+
return errAsync({ type: "deployment_cannot_be_progressed" as const });
4561
}
4662

4763
return okAsync(deployment);
4864
};
4965

50-
const createRemoteBuild = (deployment: Pick<WorkerDeployment, "id">) =>
51-
fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({
52-
type: "failed_to_create_remote_build" as const,
53-
cause: error,
54-
})).map((build) => ({
55-
id: deployment.id,
56-
externalBuildData: build,
57-
}));
58-
59-
const updateDeployment = (
60-
deployment: Pick<WorkerDeployment, "id"> & {
61-
externalBuildData: ExternalBuildData | undefined;
62-
}
63-
) =>
66+
const progressToInstalling = (deployment: Pick<WorkerDeployment, "id">) =>
6467
fromPromise(
6568
this._prisma.workerDeployment.updateMany({
6669
where: { id: deployment.id, status: "PENDING" }, // status could've changed in the meantime, we're not locking the row
6770
data: {
6871
...updates,
69-
externalBuildData: deployment.externalBuildData,
70-
status: "BUILDING",
72+
status: "INSTALLING",
7173
startedAt: new Date(),
7274
},
7375
}),
@@ -77,17 +79,51 @@ export class DeploymentService extends BaseService {
7779
})
7880
).andThen((result) => {
7981
if (result.count === 0) {
80-
return errAsync({ type: "deployment_not_pending" as const });
82+
return errAsync({ type: "deployment_cannot_be_progressed" as const });
8183
}
82-
return okAsync({ id: deployment.id });
84+
return okAsync({ id: deployment.id, status: "INSTALLING" as const });
8385
});
8486

85-
const extendTimeout = (deployment: Pick<WorkerDeployment, "id">) =>
87+
const createRemoteBuild = (deployment: Pick<WorkerDeployment, "id">) =>
88+
fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({
89+
type: "failed_to_create_remote_build" as const,
90+
cause: error,
91+
}));
92+
93+
const progressToBuilding = (deployment: Pick<WorkerDeployment, "id">) =>
94+
createRemoteBuild(deployment)
95+
.andThen((externalBuildData) =>
96+
fromPromise(
97+
this._prisma.workerDeployment.updateMany({
98+
where: { id: deployment.id, status: "INSTALLING" }, // status could've changed in the meantime, we're not locking the row
99+
data: {
100+
...updates,
101+
externalBuildData,
102+
status: "BUILDING",
103+
installedAt: new Date(),
104+
},
105+
}),
106+
(error) => ({
107+
type: "other" as const,
108+
cause: error,
109+
})
110+
)
111+
)
112+
.andThen((result) => {
113+
if (result.count === 0) {
114+
return errAsync({ type: "deployment_cannot_be_progressed" as const });
115+
}
116+
return okAsync({ id: deployment.id, status: "BUILDING" as const });
117+
});
118+
119+
const extendTimeout = (deployment: Pick<WorkerDeployment, "id" | "status">) =>
86120
fromPromise(
87121
TimeoutDeploymentService.enqueue(
88122
deployment.id,
89-
"BUILDING" satisfies WorkerDeploymentStatus,
90-
"Building timed out",
123+
deployment.status,
124+
deployment.status === "INSTALLING"
125+
? "Installing dependencies timed out"
126+
: "Building timed out",
91127
new Date(Date.now() + env.DEPLOY_TIMEOUT_MS)
92128
),
93129
(error) => ({
@@ -98,8 +134,12 @@ export class DeploymentService extends BaseService {
98134

99135
return getDeployment()
100136
.andThen(validateDeployment)
101-
.andThen(createRemoteBuild)
102-
.andThen(updateDeployment)
137+
.andThen((deployment) => {
138+
if (deployment.status === "PENDING") {
139+
return progressToInstalling(deployment);
140+
}
141+
return progressToBuilding(deployment);
142+
})
103143
.andThen(extendTimeout)
104144
.map(() => undefined);
105145
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TYPE "public"."WorkerDeploymentStatus" ADD VALUE 'INSTALLING';
2+
3+
ALTER TABLE "public"."WorkerDeployment" ADD COLUMN "installedAt" TIMESTAMP(3);

internal-packages/database/prisma/schema.prisma

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,9 +1764,10 @@ model WorkerDeployment {
17641764
triggeredBy User? @relation(fields: [triggeredById], references: [id], onDelete: SetNull, onUpdate: Cascade)
17651765
triggeredById String?
17661766
1767-
startedAt DateTime?
1768-
builtAt DateTime?
1769-
deployedAt DateTime?
1767+
startedAt DateTime?
1768+
installedAt DateTime?
1769+
builtAt DateTime?
1770+
deployedAt DateTime?
17701771
17711772
failedAt DateTime?
17721773
errorData Json?
@@ -1787,6 +1788,7 @@ model WorkerDeployment {
17871788

17881789
enum WorkerDeploymentStatus {
17891790
PENDING
1791+
INSTALLING
17901792
/// This is the status when the image is being built
17911793
BUILDING
17921794
/// This is the status when the image is built and we are waiting for the indexing to finish

packages/cli-v3/src/commands/deploy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ async function failDeploy(
676676

677677
switch (serverDeployment.status) {
678678
case "PENDING":
679+
case "INSTALLING":
679680
case "DEPLOYING":
680681
case "BUILDING": {
681682
await doOutputLogs();

packages/core/src/v3/schemas/api.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,13 +377,13 @@ export const FinalizeDeploymentRequestBody = z.object({
377377

378378
export type FinalizeDeploymentRequestBody = z.infer<typeof FinalizeDeploymentRequestBody>;
379379

380-
export const StartDeploymentRequestBody = z.object({
380+
export const ProgressDeploymentRequestBody = z.object({
381381
contentHash: z.string().optional(),
382382
gitMeta: GitMeta.optional(),
383383
runtime: z.string().optional(),
384384
});
385385

386-
export type StartDeploymentRequestBody = z.infer<typeof StartDeploymentRequestBody>;
386+
export type ProgressDeploymentRequestBody = z.infer<typeof ProgressDeploymentRequestBody>;
387387

388388
export const ExternalBuildData = z.object({
389389
buildId: z.string(),
@@ -465,6 +465,7 @@ export const GetDeploymentResponseBody = z.object({
465465
id: z.string(),
466466
status: z.enum([
467467
"PENDING",
468+
"INSTALLING",
468469
"BUILDING",
469470
"DEPLOYING",
470471
"DEPLOYED",

0 commit comments

Comments
 (0)