chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
import {
|
||||
BuildServerMetadata,
|
||||
DeploymentErrorData,
|
||||
ExternalBuildData,
|
||||
prepareDeploymentError,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import { type RuntimeEnvironment, type WorkerDeployment } from "@trigger.dev/database";
|
||||
import { type PrismaClient, prisma } from "~/db.server";
|
||||
import { type Organization } from "~/models/organization.server";
|
||||
import { type Project } from "~/models/project.server";
|
||||
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
|
||||
import { type User } from "~/models/user.server";
|
||||
import { getUsername } from "~/utils/username";
|
||||
import { processGitMetadata } from "./BranchesPresenter.server";
|
||||
import { VercelProjectIntegrationDataSchema } from "~/v3/vercel/vercelProjectIntegrationSchema";
|
||||
import { S2 } from "@s2-dev/streamstore";
|
||||
import { env } from "~/env.server";
|
||||
import { createRedisClient } from "~/redis.server";
|
||||
import { tryCatch } from "@trigger.dev/core";
|
||||
import { logger } from "~/services/logger.server";
|
||||
|
||||
const S2_TOKEN_KEY_PREFIX = "s2-token:project:";
|
||||
|
||||
const s2TokenRedis = createRedisClient("s2-token-cache", {
|
||||
host: env.CACHE_REDIS_HOST,
|
||||
port: env.CACHE_REDIS_PORT,
|
||||
username: env.CACHE_REDIS_USERNAME,
|
||||
password: env.CACHE_REDIS_PASSWORD,
|
||||
tlsDisabled: env.CACHE_REDIS_TLS_DISABLED === "true",
|
||||
clusterMode: env.CACHE_REDIS_CLUSTER_MODE_ENABLED === "1",
|
||||
});
|
||||
|
||||
const s2 = env.S2_ENABLED === "1" ? new S2({ accessToken: env.S2_ACCESS_TOKEN }) : undefined;
|
||||
|
||||
export type ErrorData = {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
stderr?: string;
|
||||
};
|
||||
|
||||
export class DeploymentPresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call({
|
||||
userId,
|
||||
projectSlug,
|
||||
organizationSlug,
|
||||
environmentSlug,
|
||||
deploymentShortCode,
|
||||
}: {
|
||||
userId: User["id"];
|
||||
projectSlug: Project["slug"];
|
||||
organizationSlug: Organization["slug"];
|
||||
environmentSlug: RuntimeEnvironment["slug"];
|
||||
deploymentShortCode: WorkerDeployment["shortCode"];
|
||||
}) {
|
||||
const project = await this.#prismaClient.project.findFirstOrThrow({
|
||||
select: {
|
||||
id: true,
|
||||
organizationId: true,
|
||||
externalRef: true,
|
||||
},
|
||||
where: {
|
||||
slug: projectSlug,
|
||||
organization: {
|
||||
slug: organizationSlug,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const environment = await findEnvironmentBySlug(project.id, environmentSlug, userId);
|
||||
if (!environment) {
|
||||
throw new Error(`Environment not found`);
|
||||
}
|
||||
|
||||
const deployment = await this.#prismaClient.workerDeployment.findFirstOrThrow({
|
||||
where: {
|
||||
projectId: project.id,
|
||||
shortCode: deploymentShortCode,
|
||||
environmentId: environment.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
shortCode: true,
|
||||
version: true,
|
||||
runtime: true,
|
||||
runtimeVersion: true,
|
||||
errorData: true,
|
||||
imageReference: true,
|
||||
imagePlatform: true,
|
||||
externalBuildData: true,
|
||||
projectId: true,
|
||||
type: true,
|
||||
environment: {
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
slug: true,
|
||||
orgMember: {
|
||||
select: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
displayName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
status: true,
|
||||
builtAt: true,
|
||||
deployedAt: true,
|
||||
createdAt: true,
|
||||
startedAt: true,
|
||||
installedAt: true,
|
||||
canceledAt: true,
|
||||
canceledReason: true,
|
||||
git: true,
|
||||
promotions: {
|
||||
select: {
|
||||
label: true,
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
select: {
|
||||
tasks: {
|
||||
select: {
|
||||
slug: true,
|
||||
filePath: true,
|
||||
},
|
||||
orderBy: {
|
||||
slug: "asc",
|
||||
},
|
||||
},
|
||||
sdkVersion: true,
|
||||
cliVersion: true,
|
||||
},
|
||||
},
|
||||
triggeredBy: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
displayName: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
},
|
||||
buildServerMetadata: true,
|
||||
triggeredVia: true,
|
||||
},
|
||||
});
|
||||
|
||||
const gitMetadata = processGitMetadata(deployment.git);
|
||||
|
||||
// Look up Vercel integration data to construct a deployment URL
|
||||
let vercelDeploymentUrl: string | undefined;
|
||||
const vercelProjectIntegration =
|
||||
await this.#prismaClient.organizationProjectIntegration.findFirst({
|
||||
where: {
|
||||
projectId: project.id,
|
||||
deletedAt: null,
|
||||
organizationIntegration: {
|
||||
service: "VERCEL",
|
||||
deletedAt: null,
|
||||
},
|
||||
},
|
||||
select: {
|
||||
integrationData: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (vercelProjectIntegration) {
|
||||
const parsed = VercelProjectIntegrationDataSchema.safeParse(
|
||||
vercelProjectIntegration.integrationData
|
||||
);
|
||||
|
||||
if (parsed.success && parsed.data.vercelTeamSlug) {
|
||||
const integrationDeployment = await this.#prismaClient.integrationDeployment.findFirst({
|
||||
where: {
|
||||
deploymentId: deployment.id,
|
||||
integrationName: "vercel",
|
||||
},
|
||||
select: {
|
||||
integrationDeploymentId: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
|
||||
if (integrationDeployment) {
|
||||
const vercelId = integrationDeployment.integrationDeploymentId;
|
||||
vercelDeploymentUrl = `https://vercel.com/${parsed.data.vercelTeamSlug}/${parsed.data.vercelProjectName}/${vercelId}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const externalBuildData = deployment.externalBuildData
|
||||
? ExternalBuildData.safeParse(deployment.externalBuildData)
|
||||
: undefined;
|
||||
const buildServerMetadata = deployment.buildServerMetadata
|
||||
? BuildServerMetadata.safeParse(deployment.buildServerMetadata)
|
||||
: undefined;
|
||||
|
||||
let eventStream = undefined;
|
||||
if (
|
||||
env.S2_ENABLED === "1" &&
|
||||
(buildServerMetadata ||
|
||||
gitMetadata?.source === "trigger_github_app" ||
|
||||
env.S2_DEPLOYMENT_STREAMS_LOCAL === "1")
|
||||
) {
|
||||
const [error, accessToken] = await tryCatch(this.getS2AccessToken(project.externalRef));
|
||||
|
||||
if (error) {
|
||||
logger.error("Failed getting S2 access token", { error });
|
||||
} else {
|
||||
eventStream = {
|
||||
s2: {
|
||||
basin: env.S2_DEPLOYMENT_LOGS_BASIN_NAME,
|
||||
stream: `projects/${project.externalRef}/deployments/${deployment.shortCode}`,
|
||||
accessToken,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
eventStream,
|
||||
deployment: {
|
||||
id: deployment.id,
|
||||
shortCode: deployment.shortCode,
|
||||
version: deployment.version,
|
||||
status: deployment.status,
|
||||
createdAt: deployment.createdAt,
|
||||
startedAt: deployment.startedAt,
|
||||
installedAt: deployment.installedAt,
|
||||
builtAt: deployment.builtAt,
|
||||
deployedAt: deployment.deployedAt,
|
||||
canceledAt: deployment.canceledAt,
|
||||
canceledReason: deployment.canceledReason,
|
||||
tasks: deployment.worker?.tasks,
|
||||
label: deployment.promotions?.[0]?.label,
|
||||
environment: {
|
||||
id: deployment.environment.id,
|
||||
type: deployment.environment.type,
|
||||
slug: deployment.environment.slug,
|
||||
userId: deployment.environment.orgMember?.user.id,
|
||||
userName: getUsername(deployment.environment.orgMember?.user),
|
||||
},
|
||||
deployedBy: deployment.triggeredBy,
|
||||
sdkVersion: deployment.worker?.sdkVersion,
|
||||
cliVersion: deployment.worker?.cliVersion,
|
||||
runtime: deployment.runtime,
|
||||
runtimeVersion: deployment.runtimeVersion,
|
||||
imageReference: deployment.imageReference,
|
||||
imagePlatform: deployment.imagePlatform,
|
||||
externalBuildData:
|
||||
externalBuildData && externalBuildData.success ? externalBuildData.data : undefined,
|
||||
projectId: deployment.projectId,
|
||||
organizationId: project.organizationId,
|
||||
errorData: DeploymentPresenter.prepareErrorData(deployment.errorData),
|
||||
isBuilt: !!deployment.builtAt,
|
||||
type: deployment.type,
|
||||
git: gitMetadata,
|
||||
triggeredVia: deployment.triggeredVia,
|
||||
vercelDeploymentUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private async getS2AccessToken(projectRef: string): Promise<string> {
|
||||
if (env.S2_ENABLED !== "1" || !s2) {
|
||||
throw new Error("Failed getting S2 access token: S2 is not enabled");
|
||||
}
|
||||
|
||||
const redisKey = `${S2_TOKEN_KEY_PREFIX}${projectRef}`;
|
||||
const cachedToken = await s2TokenRedis.get(redisKey);
|
||||
|
||||
if (cachedToken) {
|
||||
return cachedToken;
|
||||
}
|
||||
|
||||
const { accessToken } = await s2.accessTokens.issue({
|
||||
id: `${projectRef}-${new Date().getTime()}`,
|
||||
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
|
||||
scope: {
|
||||
ops: ["read"],
|
||||
basins: {
|
||||
exact: env.S2_DEPLOYMENT_LOGS_BASIN_NAME,
|
||||
},
|
||||
streams: {
|
||||
prefix: `projects/${projectRef}/deployments/`,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await s2TokenRedis.setex(
|
||||
redisKey,
|
||||
59 * 60, // slightly shorter than the token validity period
|
||||
accessToken
|
||||
);
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public static prepareErrorData(errorData: WorkerDeployment["errorData"]): ErrorData | undefined {
|
||||
if (!errorData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deploymentError = DeploymentErrorData.safeParse(errorData);
|
||||
|
||||
if (!deploymentError.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
return prepareDeploymentError(deploymentError.data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user