Skip to content

Commit 7d333e5

Browse files
authored
feat(cli): attach to existing deployments in the build server (#2501)
* Add external build data and image platform to the get deployment endpoint * If provided, attach to an existing deployment in the deploy command * Check status for existing deployments * Add changeset
1 parent 7c4ce6f commit 7d333e5

File tree

4 files changed

+85
-18
lines changed

4 files changed

+85
-18
lines changed

.changeset/seven-taxis-glow.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+
Attach to existing deployment for deployments triggered in the build server. If `TRIGGER_EXISTING_DEPLOYMENT_ID` env var is set, the `deploy` command now skips the deployment initialization.

apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { LoaderFunctionArgs, json } from "@remix-run/server-runtime";
1+
import { type LoaderFunctionArgs, json } from "@remix-run/server-runtime";
2+
import { type GetDeploymentResponseBody } from "@trigger.dev/core/v3";
23
import { z } from "zod";
34
import { prisma } from "~/db.server";
45
import { authenticateApiRequest } from "~/services/apiAuth.server";
@@ -52,7 +53,10 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
5253
shortCode: deployment.shortCode,
5354
version: deployment.version,
5455
imageReference: deployment.imageReference,
55-
errorData: deployment.errorData,
56+
imagePlatform: deployment.imagePlatform,
57+
externalBuildData:
58+
deployment.externalBuildData as GetDeploymentResponseBody["externalBuildData"],
59+
errorData: deployment.errorData as GetDeploymentResponseBody["errorData"],
5660
worker: deployment.worker
5761
? {
5862
id: deployment.worker.friendlyId,
@@ -65,5 +69,5 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
6569
})),
6670
}
6771
: undefined,
68-
});
72+
} satisfies GetDeploymentResponseBody);
6973
}

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

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { intro, log, outro } from "@clack/prompts";
22
import { getBranch, prepareDeploymentError, tryCatch } from "@trigger.dev/core/v3";
3-
import { InitializeDeploymentResponseBody } from "@trigger.dev/core/v3/schemas";
3+
import {
4+
InitializeDeploymentRequestBody,
5+
InitializeDeploymentResponseBody,
6+
} from "@trigger.dev/core/v3/schemas";
47
import { Command, Option as CommandOption } from "commander";
58
import { resolve } from "node:path";
69
import { isCI } from "std-env";
@@ -306,19 +309,17 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
306309
return;
307310
}
308311

309-
const deploymentResponse = await projectClient.client.initializeDeployment({
310-
contentHash: buildManifest.contentHash,
311-
userId: authorization.auth.tokenType === "personal" ? authorization.userId : undefined,
312-
gitMeta,
313-
type: features.run_engine_v2 ? "MANAGED" : "V1",
314-
runtime: buildManifest.runtime,
315-
});
316-
317-
if (!deploymentResponse.success) {
318-
throw new Error(`Failed to start deployment: ${deploymentResponse.error}`);
319-
}
320-
321-
const deployment = deploymentResponse.data;
312+
const deployment = await initializeOrAttachDeployment(
313+
projectClient.client,
314+
{
315+
contentHash: buildManifest.contentHash,
316+
userId: authorization.auth.tokenType === "personal" ? authorization.userId : undefined,
317+
gitMeta,
318+
type: features.run_engine_v2 ? "MANAGED" : "V1",
319+
runtime: buildManifest.runtime,
320+
},
321+
envVars.TRIGGER_EXISTING_DEPLOYMENT_ID
322+
);
322323
const isLocalBuild = !deployment.externalBuildData;
323324

324325
// Fail fast if we know local builds will fail
@@ -619,7 +620,7 @@ export async function syncEnvVarsWithServer(
619620

620621
async function failDeploy(
621622
client: CliApiClient,
622-
deployment: Deployment,
623+
deployment: Pick<Deployment, "id" | "shortCode">,
623624
error: { name: string; message: string },
624625
logs: string,
625626
$spinner: ReturnType<typeof spinner>,
@@ -735,6 +736,60 @@ async function failDeploy(
735736
}
736737
}
737738

739+
async function initializeOrAttachDeployment(
740+
apiClient: CliApiClient,
741+
data: InitializeDeploymentRequestBody,
742+
existingDeploymentId?: string
743+
): Promise<InitializeDeploymentResponseBody> {
744+
if (existingDeploymentId) {
745+
// In the build server we initialize the deployment before installing the project dependencies,
746+
// so that the status is correctly reflected in the dashboard. In this case, we need to attach
747+
// to the existing deployment and continue with the remote build process.
748+
// This is a workaround to avoid major changes in the deploy command and workflow. In the future,
749+
// we'll likely make the build server the entry point of the flow for building and deploying and also
750+
// adapt the related deployment API endpoints.
751+
752+
const existingDeploymentOrError = await apiClient.getDeployment(existingDeploymentId);
753+
754+
if (!existingDeploymentOrError.success) {
755+
throw new Error(
756+
`Failed to attach to existing deployment: ${existingDeploymentOrError.error}`
757+
);
758+
}
759+
760+
const { imageReference, status } = existingDeploymentOrError.data;
761+
if (!imageReference) {
762+
// this is just an artifact of our current DB schema
763+
// `imageReference` is stored as nullable, but it should always exist
764+
throw new Error("Existing deployment does not have an image reference");
765+
}
766+
767+
if (
768+
status === "CANCELED" ||
769+
status === "FAILED" ||
770+
status === "TIMED_OUT" ||
771+
status === "DEPLOYED"
772+
) {
773+
throw new Error(`Existing deployment is in an unexpected state: ${status}`);
774+
}
775+
776+
return {
777+
...existingDeploymentOrError.data,
778+
imageTag: imageReference,
779+
};
780+
}
781+
782+
const newDeploymentOrError = await apiClient.initializeDeployment({
783+
...data,
784+
});
785+
786+
if (!newDeploymentOrError.success) {
787+
throw new Error(`Failed to start deployment: ${newDeploymentOrError.error}`);
788+
}
789+
790+
return newDeploymentOrError.data;
791+
}
792+
738793
export function verifyDirectory(dir: string, projectPath: string) {
739794
if (dir !== "." && !isDirectory(projectPath)) {
740795
if (dir === "staging" || dir === "prod" || dir === "preview") {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,8 @@ export const GetDeploymentResponseBody = z.object({
467467
shortCode: z.string(),
468468
version: z.string(),
469469
imageReference: z.string().nullish(),
470+
imagePlatform: z.string(),
471+
externalBuildData: ExternalBuildData.optional().nullable(),
470472
errorData: DeploymentErrorData.nullish(),
471473
worker: z
472474
.object({

0 commit comments

Comments
 (0)