-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathinitializeDeployment.server.ts
More file actions
191 lines (168 loc) · 7.23 KB
/
initializeDeployment.server.ts
File metadata and controls
191 lines (168 loc) · 7.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import { type InitializeDeploymentRequestBody } from "@trigger.dev/core/v3";
import { customAlphabet } from "nanoid";
import { env } from "~/env.server";
import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { generateFriendlyId } from "../friendlyIdentifiers";
import { createRemoteImageBuild, remoteBuildsEnabled } from "../remoteImageBuilder.server";
import { calculateNextBuildVersion } from "../utils/calculateNextBuildVersion";
import { BaseService, ServiceValidationError } from "./baseService.server";
import { TimeoutDeploymentService } from "./timeoutDeployment.server";
import { getDeploymentImageRef } from "../getDeploymentImageRef.server";
import { tryCatch } from "@trigger.dev/core";
import { getRegistryConfig } from "../registryConfig.server";
const nanoid = customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 8);
export class InitializeDeploymentService extends BaseService {
public async call(
environment: AuthenticatedEnvironment,
payload: InitializeDeploymentRequestBody
) {
return this.traceWithEnv("call", environment, async () => {
if (payload.gitMeta?.commitSha?.startsWith("deployment_")) {
// When we introduced automatic deployments via the build server, we slightly changed the deployment flow
// mainly in the initialization and starting step: now deployments are first initialized in the `PENDING` status
// and updated to `BUILDING` once the build server dequeues the build job.
// Newer versions of the `deploy` command in the CLI will automatically attach to the existing deployment
// and continue with the build process. For older versions, we can't change the command's client-side behavior,
// so we need to handle this case here in the initialization endpoint. As we control the env variables which
// the git meta is extracted from in the build server, we can use those to pass the existing deployment ID
// to this endpoint. This doesn't affect the git meta on the deployment as it is set prior to this step using the
// /start endpoint. It's a rather hacky solution, but it will do for now as it enables us to avoid degrading the
// build server experience for users with older CLI versions. We'll eventually be able to remove this workaround
// once we stop supporting 3.x CLI versions.
const existingDeploymentId = payload.gitMeta.commitSha;
const existingDeployment = await this._prisma.workerDeployment.findFirst({
where: {
environmentId: environment.id,
friendlyId: existingDeploymentId,
},
});
if (!existingDeployment) {
throw new ServiceValidationError(
"Existing deployment not found during deployment initialization"
);
}
return {
deployment: existingDeployment,
imageRef: existingDeployment.imageReference ?? "",
};
}
if (payload.type === "UNMANAGED") {
throw new ServiceValidationError("UNMANAGED deployments are not supported");
}
// Upgrade the project to engine "V2" if it's not already. This should cover cases where people deploy to V2 without running dev first.
if (payload.type === "MANAGED" && environment.project.engine === "V1") {
await this._prisma.project.update({
where: {
id: environment.project.id,
},
data: {
engine: "V2",
},
});
}
const latestDeployment = await this._prisma.workerDeployment.findFirst({
where: {
environmentId: environment.id,
},
orderBy: {
createdAt: "desc",
},
take: 1,
});
const nextVersion = calculateNextBuildVersion(latestDeployment?.version);
if (payload.selfHosted && remoteBuildsEnabled()) {
throw new ServiceValidationError(
"Self-hosted deployments are not supported on this instance"
);
}
// For the `PENDING` initial status, defer the creation of the Depot build until the deployment is started.
// This helps avoid Depot token expiration issues.
const externalBuildData =
payload.initialStatus === "PENDING"
? undefined
: await createRemoteImageBuild(environment.project);
const triggeredBy = payload.userId
? await this._prisma.user.findFirst({
where: {
id: payload.userId,
orgMemberships: {
some: {
organizationId: environment.project.organizationId,
},
},
},
})
: undefined;
const isV4Deployment = payload.type === "MANAGED";
const registryConfig = getRegistryConfig(isV4Deployment);
const deploymentShortCode = nanoid(8);
const [imageRefError, imageRefResult] = await tryCatch(
getDeploymentImageRef({
registry: registryConfig,
projectRef: environment.project.externalRef,
nextVersion,
environmentType: environment.type,
deploymentShortCode,
})
);
if (imageRefError) {
logger.error("Failed to get deployment image ref", {
environmentId: environment.id,
projectId: environment.projectId,
version: nextVersion,
triggeredById: triggeredBy?.id,
type: payload.type,
cause: imageRefError.message,
});
throw new ServiceValidationError("Failed to get deployment image ref");
}
const { imageRef, isEcr, repoCreated } = imageRefResult;
// we keep using `BUILDING` as the initial status if not explicitly set
// to avoid changing the behavior for deployments not created in the build server
const initialStatus = payload.initialStatus ?? "BUILDING";
logger.debug("Creating deployment", {
environmentId: environment.id,
projectId: environment.projectId,
version: nextVersion,
triggeredById: triggeredBy?.id,
type: payload.type,
imageRef,
isEcr,
repoCreated,
initialStatus,
});
const deployment = await this._prisma.workerDeployment.create({
data: {
friendlyId: generateFriendlyId("deployment"),
contentHash: payload.contentHash,
shortCode: deploymentShortCode,
version: nextVersion,
status: initialStatus,
environmentId: environment.id,
projectId: environment.projectId,
externalBuildData,
triggeredById: triggeredBy?.id,
type: payload.type,
imageReference: imageRef,
imagePlatform: env.DEPLOY_IMAGE_PLATFORM,
git: payload.gitMeta ?? undefined,
runtime: payload.runtime ?? undefined,
startedAt: initialStatus === "BUILDING" ? new Date() : undefined,
},
});
const timeoutMs =
deployment.status === "PENDING" ? env.DEPLOY_QUEUE_TIMEOUT_MS : env.DEPLOY_TIMEOUT_MS;
await TimeoutDeploymentService.enqueue(
deployment.id,
deployment.status,
"Building timed out",
new Date(Date.now() + timeoutMs)
);
return {
deployment,
imageRef,
};
});
}
}