Skip to content

Commit 04881e4

Browse files
committed
WIP
1 parent 68a73be commit 04881e4

File tree

8 files changed

+128
-93
lines changed

8 files changed

+128
-93
lines changed

apps/webapp/app/db.server.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,14 @@ function getClient() {
107107
emit: "stdout",
108108
level: "warn",
109109
},
110-
{
111-
emit: "stdout",
112-
level: "query",
113-
},
114-
{
115-
emit: "event",
116-
level: "query",
117-
},
110+
// {
111+
// emit: "stdout",
112+
// level: "query",
113+
// },
114+
// {
115+
// emit: "event",
116+
// level: "query",
117+
// },
118118
],
119119
});
120120

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ActionFunctionArgs } from "@remix-run/server-runtime";
2+
import { z } from "zod";
3+
import { validateJWTToken } from "~/services/apiAuth.server";
4+
import { logger } from "~/services/logger.server";
5+
6+
const JWTPayloadSchema = z.object({
7+
environment_id: z.string(),
8+
org_id: z.string(),
9+
project_id: z.string(),
10+
run_id: z.string(),
11+
machine_preset: z.string(),
12+
});
13+
14+
export async function action({ request }: ActionFunctionArgs) {
15+
// Ensure this is a POST request
16+
if (request.method.toUpperCase() !== "POST") {
17+
return { status: 405, body: "Method Not Allowed" };
18+
}
19+
20+
const jwt = request.headers.get("x-trigger-jwt");
21+
22+
if (!jwt) {
23+
return { status: 401, body: "Unauthorized" };
24+
}
25+
26+
logger.debug("Validating JWT", { jwt });
27+
28+
const jwtPayload = await validateJWTToken(jwt, JWTPayloadSchema);
29+
30+
logger.debug("Validated JWT", { jwtPayload });
31+
32+
return new Response(null, {
33+
status: 200,
34+
});
35+
}

apps/webapp/app/services/apiAuth.server.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Prettify } from "@trigger.dev/core";
22
import { z } from "zod";
33
import {
4+
RuntimeEnvironment,
45
findEnvironmentByApiKey,
56
findEnvironmentByPublicApiKey,
67
} from "~/models/runtimeEnvironment.server";
@@ -12,7 +13,8 @@ import {
1213
import { prisma } from "~/db.server";
1314
import { json } from "@remix-run/server-runtime";
1415
import { findProjectByRef } from "~/models/project.server";
15-
import { SignJWT } from "jose";
16+
import { SignJWT, jwtVerify } from "jose";
17+
import { env } from "~/env.server";
1618

1719
type Optional<T, K extends keyof T> = Prettify<Omit<T, K> & Partial<Pick<T, K>>>;
1820

@@ -211,14 +213,20 @@ export async function authenticatedEnvironmentForAuthentication(
211213
}
212214
}
213215

214-
export async function generateJWTTokenForEnvironment(environment: AuthenticatedEnvironment) {
215-
const secret = new TextEncoder().encode(
216-
"cc7e0d44fd473002f1c42167459001140ec6389b7353f8088f4d9a95f2f596f2"
217-
);
216+
export async function generateJWTTokenForEnvironment(
217+
environment: RuntimeEnvironment,
218+
payload: Record<string, string>
219+
) {
220+
const secret = new TextEncoder().encode(env.SESSION_SECRET);
218221

219222
const alg = "HS256";
220223

221-
const jwt = await new SignJWT({ environment_id: environment.id })
224+
const jwt = await new SignJWT({
225+
environment_id: environment.id,
226+
org_id: environment.organizationId,
227+
project_id: environment.projectId,
228+
...payload,
229+
})
222230
.setProtectedHeader({ alg })
223231
.setIssuedAt()
224232
.setIssuer("https://id.trigger.dev")
@@ -228,3 +236,20 @@ export async function generateJWTTokenForEnvironment(environment: AuthenticatedE
228236

229237
return jwt;
230238
}
239+
240+
export async function validateJWTToken<T extends z.ZodTypeAny>(
241+
jwt: string,
242+
payloadSchema: T
243+
): Promise<z.infer<T>> {
244+
const secret = new TextEncoder().encode(env.SESSION_SECRET);
245+
246+
const { payload, protectedHeader } = await jwtVerify(jwt, secret, {
247+
issuer: "https://id.trigger.dev",
248+
audience: "https://api.trigger.dev",
249+
});
250+
251+
console.log(protectedHeader);
252+
console.log(payload);
253+
254+
return payloadSchema.parse(payload);
255+
}

apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -746,10 +746,6 @@ async function resolveBuiltInProdVariables(runtimeEnvironment: RuntimeEnvironmen
746746
key: "TRIGGER_ORG_ID",
747747
value: runtimeEnvironment.organizationId,
748748
},
749-
{
750-
key: "TRIGGER_MACHINE_PRESET",
751-
value: "tiny-1x",
752-
},
753749
];
754750

755751
if (env.PROD_OTEL_BATCH_PROCESSING_ENABLED === "1") {

apps/webapp/app/v3/marqs/sharedQueueConsumer.server.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
1515
import {
1616
BackgroundWorker,
1717
BackgroundWorkerTask,
18+
RuntimeEnvironment,
19+
TaskRun,
1820
TaskRunAttemptStatus,
1921
TaskRunStatus,
2022
} from "@trigger.dev/database";
@@ -37,6 +39,8 @@ import { CrashTaskRunService } from "../services/crashTaskRun.server";
3739
import { CreateTaskRunAttemptService } from "../services/createTaskRunAttempt.server";
3840
import { RestoreCheckpointService } from "../services/restoreCheckpoint.server";
3941
import { tracer } from "../tracer.server";
42+
import { generateJWTTokenForEnvironment } from "~/services/apiAuth.server";
43+
import { EnvironmentVariable } from "../environmentVariables/repository";
4044

4145
const WithTraceContext = z.object({
4246
traceparent: z.string().optional(),
@@ -1063,7 +1067,7 @@ class SharedQueueTasks {
10631067
},
10641068
};
10651069

1066-
const variables = await resolveVariablesForEnvironment(attempt.runtimeEnvironment);
1070+
const variables = await this.#buildEnvironmentVariables(attempt.runtimeEnvironment, taskRun);
10671071

10681072
const payload: ProdTaskRunExecutionPayload = {
10691073
execution,
@@ -1129,7 +1133,7 @@ class SharedQueueTasks {
11291133
return;
11301134
}
11311135

1132-
const variables = await resolveVariablesForEnvironment(environment);
1136+
const variables = await this.#buildEnvironmentVariables(environment, run);
11331137

11341138
return {
11351139
traceContext: run.traceContext as Record<string, unknown>,
@@ -1170,6 +1174,30 @@ class SharedQueueTasks {
11701174

11711175
await service.call(completion.id, completion);
11721176
}
1177+
1178+
async #buildEnvironmentVariables(
1179+
environment: RuntimeEnvironment,
1180+
run: TaskRun
1181+
): Promise<Array<EnvironmentVariable>> {
1182+
const variables = await resolveVariablesForEnvironment(environment);
1183+
1184+
const jwt = await generateJWTTokenForEnvironment(environment, {
1185+
run_id: run.id,
1186+
machine_present: "tiny-1x",
1187+
});
1188+
1189+
return [
1190+
...variables,
1191+
...[
1192+
{ key: "TRIGGER_JWT", value: jwt },
1193+
{ key: "TRIGGER_RUN_ID", value: run.id },
1194+
{
1195+
key: "TRIGGER_MACHINE_PRESET",
1196+
value: "tiny-1x",
1197+
},
1198+
],
1199+
];
1200+
}
11731201
}
11741202

11751203
export const sharedQueueTasks = singleton("sharedQueueTasks", () => new SharedQueueTasks());

packages/cli-v3/src/workers/prod/worker-facade.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,13 @@ import {
4949
import { ProdRuntimeManager } from "@trigger.dev/core/v3/prod";
5050

5151
const heartbeatIntervalMs = getEnvVar("USAGE_HEARTBEAT_INTERVAL_MS");
52-
const subject = getEnvVar("TRIGGER_ORG_ID");
53-
const machinePreset = getEnvVar("TRIGGER_MACHINE_PRESET");
54-
const openMeterApiKey = getEnvVar("USAGE_OPEN_METER_API_KEY");
55-
const openMeterBaseUrl = getEnvVar("USAGE_OPEN_METER_BASE_URL");
52+
const usageEventUrl = getEnvVar("USAGE_EVENT_URL");
53+
const triggerJWT = getEnvVar("TRIGGER_JWT");
5654

5755
const prodUsageManager = new ProdUsageManager(new DevUsageManager(), {
5856
heartbeatIntervalMs: heartbeatIntervalMs ? parseInt(heartbeatIntervalMs, 10) : undefined,
59-
subject: subject!,
60-
machinePreset: machinePreset,
61-
client:
62-
openMeterApiKey && openMeterBaseUrl
63-
? {
64-
token: openMeterApiKey,
65-
baseUrl: openMeterBaseUrl,
66-
}
67-
: undefined,
57+
url: usageEventUrl,
58+
jwt: triggerJWT,
6859
});
6960

7061
usage.setGlobalUsageManager(prodUsageManager);

packages/core/src/v3/usage/prodUsageManager.ts

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { setInterval } from "node:timers/promises";
22
import { UsageManager, UsageMeasurement, UsageSample } from "./types";
3-
import { UsageClient, UsageClientOptions } from "./usageClient";
3+
import { UsageClient } from "./usageClient";
44

55
export type ProdUsageManagerOptions = {
66
heartbeatIntervalMs?: number;
7-
client?: UsageClientOptions;
8-
subject: string;
9-
machinePreset?: string;
7+
url?: string;
8+
jwt?: string;
109
};
1110

1211
export class ProdUsageManager implements UsageManager {
@@ -19,13 +18,13 @@ export class ProdUsageManager implements UsageManager {
1918
private readonly delegageUsageManager: UsageManager,
2019
private readonly options: ProdUsageManagerOptions
2120
) {
22-
if (typeof this.options.client !== "undefined") {
23-
this._usageClient = new UsageClient(this.options.client);
21+
if (this.options.url && this.options.jwt) {
22+
this._usageClient = new UsageClient(this.options.url, this.options.jwt);
2423
}
2524
}
2625

2726
get isReportingEnabled() {
28-
return typeof this.options.client !== "undefined";
27+
return typeof this._usageClient !== "undefined";
2928
}
3029

3130
disable(): void {
@@ -95,9 +94,6 @@ export class ProdUsageManager implements UsageManager {
9594
}
9695

9796
const sample = this._measurement.sample();
98-
const wallTimeSinceLastSample = this._lastSample
99-
? sample.wallTime - this._lastSample.wallTime
100-
: sample.wallTime;
10197

10298
const cpuTimeSinceLastSample = this._lastSample
10399
? sample.cpuTime - this._lastSample.cpuTime
@@ -106,10 +102,7 @@ export class ProdUsageManager implements UsageManager {
106102
this._lastSample = sample;
107103

108104
console.log("Reporting usage", {
109-
wallTimeSinceLastSample,
110105
cpuTimeSinceLastSample,
111-
subject: this.options.subject,
112-
machine: this.options.machinePreset,
113106
});
114107

115108
if (cpuTimeSinceLastSample <= 0) {
@@ -118,24 +111,12 @@ export class ProdUsageManager implements UsageManager {
118111

119112
const now = performance.now();
120113

121-
const event = {
122-
source: "prod-usage-manager",
123-
type: "usage",
124-
subject: this.options.subject,
125-
data: {
126-
durationMs: cpuTimeSinceLastSample,
127-
wallTimeInMs: wallTimeSinceLastSample,
128-
machinePreset: this.options.machinePreset ?? "unknown",
129-
},
130-
};
131-
132-
await client.sendUsageEvent(event);
114+
await client.sendUsageEvent({ durationMs: cpuTimeSinceLastSample });
133115

134116
const durationInMs = performance.now() - now;
135117

136118
console.log("Reported usage", {
137119
durationInMs,
138-
event,
139120
});
140121
}
141122
}

packages/core/src/v3/usage/usageClient.ts

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,28 @@ export type UsageClientOptions = {
44
};
55

66
export type UsageEvent = {
7-
source: string;
8-
subject: string;
9-
type: string;
10-
id?: string;
11-
time?: Date;
12-
data?: Record<string, unknown>;
7+
durationMs: number;
138
};
149

1510
export class UsageClient {
16-
constructor(private readonly options: UsageClientOptions) {}
11+
constructor(
12+
private readonly url: string,
13+
private readonly jwt: string
14+
) {}
1715

1816
async sendUsageEvent(event: UsageEvent): Promise<void> {
19-
const body = {
20-
specversion: "1.0",
21-
id: event.id ?? globalThis.crypto.randomUUID(),
22-
source: event.source,
23-
type: event.type,
24-
time: (event.time ?? new Date()).toISOString(),
25-
subject: event.subject,
26-
datacontenttype: "application/json",
27-
data: event.data,
28-
};
29-
30-
const url = `${this.baseUrl}/api/v1/events`;
31-
3217
try {
33-
await fetch(url, {
18+
await fetch(this.url, {
3419
method: "POST",
35-
body: JSON.stringify(body),
20+
body: JSON.stringify(event),
3621
headers: {
37-
"Content-Type": "application/cloudevents+json",
38-
Authorization: `Bearer ${this.token}`,
39-
Accept: "application/json",
22+
"content-type": "application/json",
23+
"x-trigger-jwt": `Bearer ${this.jwt}`,
24+
accept: "application/json",
4025
},
4126
});
42-
} catch {}
43-
}
44-
45-
get baseUrl(): string {
46-
return this.options.baseUrl;
47-
}
48-
49-
private get token(): string {
50-
return this.options.token;
27+
} catch (error) {
28+
console.error(`Failed to send usage event: ${error}`);
29+
}
5130
}
5231
}

0 commit comments

Comments
 (0)