From 0af367fc0db50ceb1ec7a04d2e9e189a136f076b Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Thu, 8 May 2025 16:56:41 +0100 Subject: [PATCH 1/4] improve route to add worker group, handles existing groups gracefully --- .../webapp/app/routes/admin.api.v1.workers.ts | 139 ++++++++++++++++-- 1 file changed, 123 insertions(+), 16 deletions(-) diff --git a/apps/webapp/app/routes/admin.api.v1.workers.ts b/apps/webapp/app/routes/admin.api.v1.workers.ts index 9299c0e2c0..04d96d5830 100644 --- a/apps/webapp/app/routes/admin.api.v1.workers.ts +++ b/apps/webapp/app/routes/admin.api.v1.workers.ts @@ -1,4 +1,6 @@ -import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { tryCatch } from "@trigger.dev/core"; +import { type Project } from "@trigger.dev/database"; import { z } from "zod"; import { prisma } from "~/db.server"; import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server"; @@ -18,7 +20,7 @@ export async function action({ request }: ActionFunctionArgs) { return json({ error: "Invalid or Missing API key" }, { status: 401 }); } - const user = await prisma.user.findUnique({ + const user = await prisma.user.findFirst({ where: { id: authenticationResult.userId, }, @@ -36,28 +38,133 @@ export async function action({ request }: ActionFunctionArgs) { const rawBody = await request.json(); const { name, description, makeDefaultForProjectId } = RequestBodySchema.parse(rawBody ?? {}); - const service = new WorkerGroupService(); - const { workerGroup, token } = await service.createWorkerGroup({ - name, - description, + const existingWorkerGroup = await prisma.workerInstanceGroup.findFirst({ + where: { + // We only check managed worker groups + masterQueue: name, + }, }); - if (makeDefaultForProjectId) { - await prisma.project.update({ - where: { - id: makeDefaultForProjectId, + if (!existingWorkerGroup) { + const { workerGroup, token } = await createWorkerGroup(name, description); + + if (!makeDefaultForProjectId) { + return json({ + outcome: "created new worker group", + token, + workerGroup, + }); + } + + const updated = await setWorkerGroupAsDefaultForProject( + workerGroup.id, + makeDefaultForProjectId + ); + + if (!updated.success) { + return json({ error: updated.error }, { status: 400 }); + } + + return json({ + outcome: "set new worker group as default for project", + token, + workerGroup, + project: updated.project, + }); + } + + if (!makeDefaultForProjectId) { + return json( + { + error: "worker group already exists", + workerGroup: existingWorkerGroup, }, - data: { - defaultWorkerGroupId: workerGroup.id, + { status: 400 } + ); + } + + const updated = await setWorkerGroupAsDefaultForProject( + existingWorkerGroup.id, + makeDefaultForProjectId + ); + + if (!updated.success) { + return json( + { + error: `failed to set worker group as default for project: ${updated.error}`, + workerGroup: existingWorkerGroup, }, - }); + { status: 400 } + ); } return json({ - token, - workerGroup, + outcome: "set existing worker group as default for project", + workerGroup: existingWorkerGroup, + project: updated.project, }); } catch (error) { - return json({ error: error instanceof Error ? error.message : error }, { status: 400 }); + return json( + { + outcome: "unknown error", + error: error instanceof Error ? error.message : error, + }, + { status: 400 } + ); + } +} + +async function createWorkerGroup(name: string | undefined, description: string | undefined) { + const service = new WorkerGroupService(); + return await service.createWorkerGroup({ name, description }); +} + +async function setWorkerGroupAsDefaultForProject( + workerGroupId: string, + projectId: string +): Promise< + | { + success: false; + error: string; + } + | { + success: true; + project: Project; + } +> { + const project = await prisma.project.findFirst({ + where: { + id: projectId, + }, + }); + + if (!project) { + return { + success: false, + error: "project not found", + }; + } + + const [error] = await tryCatch( + prisma.project.update({ + where: { + id: projectId, + }, + data: { + defaultWorkerGroupId: workerGroupId, + }, + }) + ); + + if (error) { + return { + success: false, + error: error instanceof Error ? error.message : error, + }; } + + return { + success: true, + project, + }; } From 47f7819fab5dfa709157781109ecfac24a494df1 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Thu, 8 May 2025 17:07:39 +0100 Subject: [PATCH 2/4] add option to remove default worker group from project --- .../webapp/app/routes/admin.api.v1.workers.ts | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/apps/webapp/app/routes/admin.api.v1.workers.ts b/apps/webapp/app/routes/admin.api.v1.workers.ts index 04d96d5830..7fefc189bc 100644 --- a/apps/webapp/app/routes/admin.api.v1.workers.ts +++ b/apps/webapp/app/routes/admin.api.v1.workers.ts @@ -10,6 +10,7 @@ const RequestBodySchema = z.object({ name: z.string().optional(), description: z.string().optional(), makeDefaultForProjectId: z.string().optional(), + removeDefaultFromProject: z.boolean().default(false), }); export async function action({ request }: ActionFunctionArgs) { @@ -36,7 +37,34 @@ export async function action({ request }: ActionFunctionArgs) { try { const rawBody = await request.json(); - const { name, description, makeDefaultForProjectId } = RequestBodySchema.parse(rawBody ?? {}); + const { name, description, makeDefaultForProjectId, removeDefaultFromProject } = + RequestBodySchema.parse(rawBody ?? {}); + + if (removeDefaultFromProject) { + if (!makeDefaultForProjectId) { + return json( + { + error: + "makeDefaultForProjectId is required to remove default worker group from project", + }, + { status: 400 } + ); + } + + const updated = await removeDefaultWorkerGroupFromProject(makeDefaultForProjectId); + + if (!updated.success) { + return json( + { error: `failed to remove default worker group from project: ${updated.error}` }, + { status: 400 } + ); + } + + return json({ + outcome: "removed default worker group from project", + project: updated.project, + }); + } const existingWorkerGroup = await prisma.workerInstanceGroup.findFirst({ where: { @@ -119,6 +147,44 @@ async function createWorkerGroup(name: string | undefined, description: string | return await service.createWorkerGroup({ name, description }); } +async function removeDefaultWorkerGroupFromProject(projectId: string) { + const project = await prisma.project.findFirst({ + where: { + id: projectId, + }, + }); + + if (!project) { + return { + success: false, + error: "project not found", + }; + } + + const [error] = await tryCatch( + prisma.project.update({ + where: { + id: projectId, + }, + data: { + defaultWorkerGroupId: null, + }, + }) + ); + + if (error) { + return { + success: false, + error: error instanceof Error ? error.message : error, + }; + } + + return { + success: true, + project, + }; +} + async function setWorkerGroupAsDefaultForProject( workerGroupId: string, projectId: string From 5aeeb7d5e9b382215a6e926ab790fa850ac03fa2 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Thu, 8 May 2025 17:24:30 +0100 Subject: [PATCH 3/4] separate project id field --- .../webapp/app/routes/admin.api.v1.workers.ts | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/apps/webapp/app/routes/admin.api.v1.workers.ts b/apps/webapp/app/routes/admin.api.v1.workers.ts index 7fefc189bc..b215d8ce22 100644 --- a/apps/webapp/app/routes/admin.api.v1.workers.ts +++ b/apps/webapp/app/routes/admin.api.v1.workers.ts @@ -9,7 +9,8 @@ import { WorkerGroupService } from "~/v3/services/worker/workerGroupService.serv const RequestBodySchema = z.object({ name: z.string().optional(), description: z.string().optional(), - makeDefaultForProjectId: z.string().optional(), + projectId: z.string().optional(), + makeDefaultForProject: z.boolean().default(false), removeDefaultFromProject: z.boolean().default(false), }); @@ -37,21 +38,20 @@ export async function action({ request }: ActionFunctionArgs) { try { const rawBody = await request.json(); - const { name, description, makeDefaultForProjectId, removeDefaultFromProject } = + const { name, description, projectId, makeDefaultForProject, removeDefaultFromProject } = RequestBodySchema.parse(rawBody ?? {}); if (removeDefaultFromProject) { - if (!makeDefaultForProjectId) { + if (!projectId) { return json( { - error: - "makeDefaultForProjectId is required to remove default worker group from project", + error: "projectId is required to remove default worker group from project", }, { status: 400 } ); } - const updated = await removeDefaultWorkerGroupFromProject(makeDefaultForProjectId); + const updated = await removeDefaultWorkerGroupFromProject(projectId); if (!updated.success) { return json( @@ -76,7 +76,7 @@ export async function action({ request }: ActionFunctionArgs) { if (!existingWorkerGroup) { const { workerGroup, token } = await createWorkerGroup(name, description); - if (!makeDefaultForProjectId) { + if (!makeDefaultForProject) { return json({ outcome: "created new worker group", token, @@ -84,10 +84,14 @@ export async function action({ request }: ActionFunctionArgs) { }); } - const updated = await setWorkerGroupAsDefaultForProject( - workerGroup.id, - makeDefaultForProjectId - ); + if (!projectId) { + return json( + { error: "projectId is required to set worker group as default for project" }, + { status: 400 } + ); + } + + const updated = await setWorkerGroupAsDefaultForProject(workerGroup.id, projectId); if (!updated.success) { return json({ error: updated.error }, { status: 400 }); @@ -101,7 +105,7 @@ export async function action({ request }: ActionFunctionArgs) { }); } - if (!makeDefaultForProjectId) { + if (!makeDefaultForProject) { return json( { error: "worker group already exists", @@ -111,10 +115,14 @@ export async function action({ request }: ActionFunctionArgs) { ); } - const updated = await setWorkerGroupAsDefaultForProject( - existingWorkerGroup.id, - makeDefaultForProjectId - ); + if (!projectId) { + return json( + { error: "projectId is required to set worker group as default for project" }, + { status: 400 } + ); + } + + const updated = await setWorkerGroupAsDefaultForProject(existingWorkerGroup.id, projectId); if (!updated.success) { return json( From d2f8636f742713510c072671afb85705939f08a8 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Fri, 9 May 2025 10:16:02 +0100 Subject: [PATCH 4/4] update supervisor readme with route changes --- apps/supervisor/README.md | 54 +++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/apps/supervisor/README.md b/apps/supervisor/README.md index e3bad3dcb6..2d225ca186 100644 --- a/apps/supervisor/README.md +++ b/apps/supervisor/README.md @@ -19,6 +19,8 @@ curl -sS \ -d "{\"name\": \"$wg_name\"}" ``` +If the worker group is newly created, the response will include a `token` field. If the group already exists, no token is returned. + 2. Create `.env` and set the worker token ```sh @@ -43,16 +45,52 @@ pnpm exec trigger deploy --self-hosted pnpm exec trigger deploy --self-hosted --network host ``` -## Additional worker groups +## Worker group management -When adding more worker groups you might also want to make them the default for a specific project. This will allow you to test it without having to change the global default: +### Shared variables ```sh api_url=http://localhost:3030 +admin_pat=tr_pat_... # edit this +``` + +- These are used by all commands + +### Create a worker group + +```sh wg_name=my-worker -# edit these -admin_pat=tr_pat_... +curl -sS \ + -X POST \ + "$api_url/admin/api/v1/workers" \ + -H "Authorization: Bearer $admin_pat" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"$wg_name\"}" +``` + +- If the worker group already exists, no token will be returned + +### Set a worker group as default for a project + +```sh +wg_name=my-worker +project_id=clsw6q8wz... + +curl -sS \ + -X POST \ + "$api_url/admin/api/v1/workers" \ + -H "Authorization: Bearer $admin_pat" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"$wg_name\", \"projectId\": \"$project_id\", \"makeDefaultForProject\": true}" +``` + +- If the worker group doesn't exist, yet it will be created +- If the worker group already exists, it will be attached to the project as default. No token will be returned. + +### Remove the default worker group from a project + +```sh project_id=clsw6q8wz... curl -sS \ @@ -60,8 +98,8 @@ curl -sS \ "$api_url/admin/api/v1/workers" \ -H "Authorization: Bearer $admin_pat" \ -H "Content-Type: application/json" \ - -d "{ - \"name\": \"$wg_name\", - \"makeDefaultForProjectId\": \"$project_id\" - }" + -d "{\"projectId\": \"$project_id\", \"removeDefaultFromProject\": true}" ``` + +- The project will then use the global default again +- When `removeDefaultFromProject: true` no other actions will be performed