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
6 changes: 6 additions & 0 deletions .changeset/kind-kids-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---

Added INSTALLING status to the deployment status enum.
7 changes: 7 additions & 0 deletions apps/webapp/app/components/runs/v3/DeploymentStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export function DeploymentStatusIcon({
return (
<RectangleStackIcon className={cn(deploymentStatusClassNameColor(status), className)} />
);
case "INSTALLING":
case "BUILDING":
case "DEPLOYING":
return <Spinner className={cn(deploymentStatusClassNameColor(status), className)} />;
Expand All @@ -78,6 +79,7 @@ export function deploymentStatusClassNameColor(status: WorkerDeploymentStatus):
switch (status) {
case "PENDING":
return "text-charcoal-500";
case "INSTALLING":
case "BUILDING":
case "DEPLOYING":
return "text-pending";
Expand All @@ -98,6 +100,8 @@ export function deploymentStatusTitle(status: WorkerDeploymentStatus, isBuilt: b
switch (status) {
case "PENDING":
return "Queued…";
case "INSTALLING":
return "Installing…";
case "BUILDING":
return "Building…";
case "DEPLOYING":
Expand Down Expand Up @@ -127,6 +131,7 @@ export function deploymentStatusTitle(status: WorkerDeploymentStatus, isBuilt: b
// PENDING and CANCELED are not used so are ommited from the UI
export const deploymentStatuses: WorkerDeploymentStatus[] = [
"PENDING",
"INSTALLING",
"BUILDING",
"DEPLOYING",
"DEPLOYED",
Expand All @@ -138,6 +143,8 @@ export function deploymentStatusDescription(status: WorkerDeploymentStatus): str
switch (status) {
case "PENDING":
return "The deployment is queued and waiting to be processed.";
case "INSTALLING":
return "The project dependencies are being installed.";
case "BUILDING":
return "The code is being built and prepared for deployment.";
case "DEPLOYING":
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
import { StartDeploymentRequestBody } from "@trigger.dev/core/v3";
import { ProgressDeploymentRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { authenticateRequest } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
Expand Down Expand Up @@ -35,7 +35,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
const { deploymentId } = parsedParams.data;

const rawBody = await request.json();
const body = StartDeploymentRequestBody.safeParse(rawBody);
const body = ProgressDeploymentRequestBody.safeParse(rawBody);

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

return await deploymentService
.startDeployment(authenticatedEnv, deploymentId, {
.progressDeployment(authenticatedEnv, deploymentId, {
contentHash: body.data.contentHash,
git: body.data.gitMeta,
runtime: body.data.runtime,
Expand All @@ -59,8 +59,11 @@ export async function action({ request, params }: ActionFunctionArgs) {
return new Response(null, { status: 204 }); // ignore these errors for now
case "deployment_not_found":
return json({ error: "Deployment not found" }, { status: 404 });
case "deployment_not_pending":
return json({ error: "Deployment is not pending" }, { status: 409 });
case "deployment_cannot_be_progressed":
return json(
{ error: "Deployment is not in a progressable state (PENDING or INSTALLING)" },
{ status: 409 }
);
case "failed_to_create_remote_build":
return json({ error: "Failed to create remote build" }, { status: 500 });
case "other":
Expand Down
98 changes: 69 additions & 29 deletions apps/webapp/app/v3/services/deployment.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@ import { env } from "~/env.server";
import { createRemoteImageBuild } from "../remoteImageBuilder.server";

export class DeploymentService extends BaseService {
public startDeployment(
/**
* Progresses a deployment from PENDING to INSTALLING and then to BUILDING.
* Also extends the deployment timeout.
*
* When progressing to BUILDING, the remote Depot build is also created.
*
* Only acts when the current status allows. Not idempotent.
*
* @param authenticatedEnv The environment which the deployment belongs to.
* @param friendlyId The friendly deployment ID.
* @param updates Optional deployment details to persist.
*/

public progressDeployment(
authenticatedEnv: AuthenticatedEnvironment,
friendlyId: string,
updates: Partial<Pick<WorkerDeployment, "contentHash" | "runtime"> & { git: GitMeta }>
Expand Down Expand Up @@ -37,37 +50,26 @@ export class DeploymentService extends BaseService {
});

const validateDeployment = (deployment: Pick<WorkerDeployment, "id" | "status">) => {
if (deployment.status !== "PENDING") {
logger.warn("Attempted starting deployment that is not in PENDING status", {
deployment,
});
return errAsync({ type: "deployment_not_pending" as const });
if (deployment.status !== "PENDING" && deployment.status !== "INSTALLING") {
logger.warn(
"Attempted progressing deployment that is not in PENDING or INSTALLING status",
{
deployment,
}
);
return errAsync({ type: "deployment_cannot_be_progressed" as const });
}

return okAsync(deployment);
};

const createRemoteBuild = (deployment: Pick<WorkerDeployment, "id">) =>
fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({
type: "failed_to_create_remote_build" as const,
cause: error,
})).map((build) => ({
id: deployment.id,
externalBuildData: build,
}));

const updateDeployment = (
deployment: Pick<WorkerDeployment, "id"> & {
externalBuildData: ExternalBuildData | undefined;
}
) =>
const progressToInstalling = (deployment: Pick<WorkerDeployment, "id">) =>
fromPromise(
this._prisma.workerDeployment.updateMany({
where: { id: deployment.id, status: "PENDING" }, // status could've changed in the meantime, we're not locking the row
data: {
...updates,
externalBuildData: deployment.externalBuildData,
status: "BUILDING",
status: "INSTALLING",
startedAt: new Date(),
},
}),
Expand All @@ -77,17 +79,51 @@ export class DeploymentService extends BaseService {
})
).andThen((result) => {
if (result.count === 0) {
return errAsync({ type: "deployment_not_pending" as const });
return errAsync({ type: "deployment_cannot_be_progressed" as const });
}
return okAsync({ id: deployment.id });
return okAsync({ id: deployment.id, status: "INSTALLING" as const });
});

const extendTimeout = (deployment: Pick<WorkerDeployment, "id">) =>
const createRemoteBuild = (deployment: Pick<WorkerDeployment, "id">) =>
fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({
type: "failed_to_create_remote_build" as const,
cause: error,
}));

const progressToBuilding = (deployment: Pick<WorkerDeployment, "id">) =>
createRemoteBuild(deployment)
.andThen((externalBuildData) =>
fromPromise(
this._prisma.workerDeployment.updateMany({
where: { id: deployment.id, status: "INSTALLING" }, // status could've changed in the meantime, we're not locking the row
data: {
...updates,
externalBuildData,
status: "BUILDING",
installedAt: new Date(),
},
}),
(error) => ({
type: "other" as const,
cause: error,
})
)
)
.andThen((result) => {
if (result.count === 0) {
return errAsync({ type: "deployment_cannot_be_progressed" as const });
}
return okAsync({ id: deployment.id, status: "BUILDING" as const });
});

const extendTimeout = (deployment: Pick<WorkerDeployment, "id" | "status">) =>
fromPromise(
TimeoutDeploymentService.enqueue(
deployment.id,
"BUILDING" satisfies WorkerDeploymentStatus,
"Building timed out",
deployment.status,
deployment.status === "INSTALLING"
? "Installing dependencies timed out"
: "Building timed out",
new Date(Date.now() + env.DEPLOY_TIMEOUT_MS)
),
(error) => ({
Expand All @@ -98,8 +134,12 @@ export class DeploymentService extends BaseService {

return getDeployment()
.andThen(validateDeployment)
.andThen(createRemoteBuild)
.andThen(updateDeployment)
.andThen((deployment) => {
if (deployment.status === "PENDING") {
return progressToInstalling(deployment);
}
return progressToBuilding(deployment);
})
.andThen(extendTimeout)
.map(() => undefined);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TYPE "public"."WorkerDeploymentStatus" ADD VALUE 'INSTALLING';

ALTER TABLE "public"."WorkerDeployment" ADD COLUMN "installedAt" TIMESTAMP(3);
8 changes: 5 additions & 3 deletions internal-packages/database/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -1764,9 +1764,10 @@ model WorkerDeployment {
triggeredBy User? @relation(fields: [triggeredById], references: [id], onDelete: SetNull, onUpdate: Cascade)
triggeredById String?

startedAt DateTime?
builtAt DateTime?
deployedAt DateTime?
startedAt DateTime?
installedAt DateTime?
builtAt DateTime?
deployedAt DateTime?

failedAt DateTime?
errorData Json?
Expand All @@ -1787,6 +1788,7 @@ model WorkerDeployment {

enum WorkerDeploymentStatus {
PENDING
INSTALLING
/// This is the status when the image is being built
BUILDING
/// This is the status when the image is built and we are waiting for the indexing to finish
Expand Down
1 change: 1 addition & 0 deletions packages/cli-v3/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ async function failDeploy(

switch (serverDeployment.status) {
case "PENDING":
case "INSTALLING":
case "DEPLOYING":
case "BUILDING": {
await doOutputLogs();
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/v3/schemas/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,13 @@ export const FinalizeDeploymentRequestBody = z.object({

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

export const StartDeploymentRequestBody = z.object({
export const ProgressDeploymentRequestBody = z.object({
contentHash: z.string().optional(),
gitMeta: GitMeta.optional(),
runtime: z.string().optional(),
});

export type StartDeploymentRequestBody = z.infer<typeof StartDeploymentRequestBody>;
export type ProgressDeploymentRequestBody = z.infer<typeof ProgressDeploymentRequestBody>;

export const ExternalBuildData = z.object({
buildId: z.string(),
Expand Down Expand Up @@ -465,6 +465,7 @@ export const GetDeploymentResponseBody = z.object({
id: z.string(),
status: z.enum([
"PENDING",
"INSTALLING",
"BUILDING",
"DEPLOYING",
"DEPLOYED",
Expand Down
Loading