From 6e1b8a11d464617201d889325f3fe2ead2e5bf6e Mon Sep 17 00:00:00 2001 From: hmacr Date: Sun, 15 Oct 2023 19:06:03 +0530 Subject: [PATCH 1/6] feat: allow cancelling job runs for an event-id --- .changeset/soft-ties-turn.md | 6 ++ .../api.v1.events.$eventId.cancel-runs.ts | 49 +++++++++++++ .../events/cancelRunsForEvent.server.ts | 70 +++++++++++++++++++ docs/mint.json | 1 + .../instancemethods/cancel-runs-for-event.mdx | 41 +++++++++++ docs/sdk/triggerclient/overview.mdx | 4 ++ packages/core/src/schemas/events.ts | 7 ++ packages/trigger-sdk/src/apiClient.ts | 21 ++++++ packages/trigger-sdk/src/triggerClient.ts | 4 ++ 9 files changed, 203 insertions(+) create mode 100644 .changeset/soft-ties-turn.md create mode 100644 apps/webapp/app/routes/api.v1.events.$eventId.cancel-runs.ts create mode 100644 apps/webapp/app/services/events/cancelRunsForEvent.server.ts create mode 100644 docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx diff --git a/.changeset/soft-ties-turn.md b/.changeset/soft-ties-turn.md new file mode 100644 index 000000000..0f9393f1e --- /dev/null +++ b/.changeset/soft-ties-turn.md @@ -0,0 +1,6 @@ +--- +"@trigger.dev/sdk": patch +"@trigger.dev/core": patch +--- + +implement functionality to cancel job runs triggered by a given eventId. diff --git a/apps/webapp/app/routes/api.v1.events.$eventId.cancel-runs.ts b/apps/webapp/app/routes/api.v1.events.$eventId.cancel-runs.ts new file mode 100644 index 000000000..ad47ab183 --- /dev/null +++ b/apps/webapp/app/routes/api.v1.events.$eventId.cancel-runs.ts @@ -0,0 +1,49 @@ +import type { ActionArgs } from "@remix-run/server-runtime"; +import { json } from "@remix-run/server-runtime"; +import { z } from "zod"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { CancelEventService } from "~/services/events/cancelEvent.server"; +import { logger } from "~/services/logger.server"; +import { CancelRunsForEventService } from "~/services/events/cancelRunsForEvent.server"; + +const ParamsSchema = z.object({ + eventId: z.string(), +}); + +export async function action({ request, params }: ActionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "POST") { + return { status: 405, body: "Method Not Allowed" }; + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const authenticatedEnv = authenticationResult.environment; + + const parsed = ParamsSchema.safeParse(params); + + if (!parsed.success) { + return json({ error: "Invalid or Missing eventId" }, { status: 400 }); + } + + const { eventId } = parsed.data; + + const service = new CancelRunsForEventService(); + try { + const res = await service.call(authenticatedEnv, eventId); + + if (!res) { + return json({ error: "Event not found" }, { status: 404 }); + } + + return json(res); + } catch (err) { + logger.error("CancelRunsForEventService.call() error", { error: err }); + return json({ error: "Internal Server Error" }, { status: 500 }); + } +} diff --git a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts new file mode 100644 index 000000000..8d479b47f --- /dev/null +++ b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts @@ -0,0 +1,70 @@ +import { $transaction, PrismaClient, prisma } from "~/db.server"; +import { AuthenticatedEnvironment } from "../apiAuth.server"; +import { JobRunStatus } from "@trigger.dev/database"; +import { CancelRunService } from "../runs/cancelRun.server"; +import { logger } from "../logger.server"; +import { CancelRunsForEvent } from "@trigger.dev/core/schemas/events"; + +const CANCELLABLE_JOB_RUN_STATUS: JobRunStatus[] = [ + JobRunStatus.PENDING, + JobRunStatus.QUEUED, + JobRunStatus.WAITING_ON_CONNECTIONS, + JobRunStatus.PREPROCESSING, + JobRunStatus.STARTED, +]; + +export class CancelRunsForEventService { + #prismaClient: PrismaClient; + + constructor(prismaClient: PrismaClient = prisma) { + this.#prismaClient = prismaClient; + } + + public async call(environment: AuthenticatedEnvironment, eventId: string) { + return await $transaction(this.#prismaClient, async (tx) => { + const event = await tx.eventRecord.findUnique({ + where: { + eventId_environmentId: { + eventId: eventId, + environmentId: environment.id, + }, + }, + }); + + if (!event) { + return; + } + + const jobRuns = await tx.jobRun.findMany({ + where: { + eventId: event.id, + status: { + in: CANCELLABLE_JOB_RUN_STATUS, + }, + }, + select: { + id: true, + }, + }); + + const cancelRunService = new CancelRunService(this.#prismaClient); + const cancelledRunIds: string[] = []; + const failedToCancelRunIds: string[] = []; + + for (const jobRun of jobRuns) { + try { + await cancelRunService.call({ runId: jobRun.id }); + cancelledRunIds.push(jobRun.id); + } catch (err) { + logger.debug(`failed to cancel job run with id ${jobRun.id} for event id ${eventId}`); + failedToCancelRunIds.push(jobRun.id); + } + } + + return { + cancelled_run_ids: cancelledRunIds, + failed_to_cancel_run_ids: failedToCancelRunIds, + }; + }); + } +} diff --git a/docs/mint.json b/docs/mint.json index 58228c57e..ef5e50bca 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -312,6 +312,7 @@ "sdk/triggerclient/instancemethods/sendevent", "sdk/triggerclient/instancemethods/getevent", "sdk/triggerclient/instancemethods/cancel-event", + "sdk/triggerclient/instancemethods/cancel-runs-for-event", "sdk/triggerclient/instancemethods/getruns", "sdk/triggerclient/instancemethods/getrun", "sdk/triggerclient/instancemethods/define-job", diff --git a/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx b/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx new file mode 100644 index 000000000..790846a5d --- /dev/null +++ b/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx @@ -0,0 +1,41 @@ +--- +title: "TriggerClient: cancelRunsForEvent() Instance Method" +sidebarTitle: "cancelRunsForEvent()" +description: "The `cancelRunsForEvent()` instance method will cancel all the job runs (yet to be executed) that are triggered by a given eventId." +--- + +## Parameters + + + The event ID to cancel the job runs for. This is returned when calling either + [client.sendEvent()](/sdk/triggerclient/instancemethods/sendevent) or + [io.sendEvent()](/sdk/io/sendevent). + + +## Returns + + + + + List of Job Run IDs that are cancelled. + + + List of Job Run IDs that have been failed to be cancelled. + + + + + + +```ts Cancelling Runs for an Event +const event = client.sendEvent({ + name: "test.job", +}); + +const res = client.cancelRunsForEvent(event.id); + +console.log(res.cancelled_run_ids); +console.log(res.failed_to_cancel_run_ids); +``` + + diff --git a/docs/sdk/triggerclient/overview.mdx b/docs/sdk/triggerclient/overview.mdx index 44b9e3311..f57cb50dd 100644 --- a/docs/sdk/triggerclient/overview.mdx +++ b/docs/sdk/triggerclient/overview.mdx @@ -46,6 +46,10 @@ The `getEvent()` method gets the event details for a given eventId. The `cancelEvent()` method cancels an event that is scheduled to be delivered in the future. +#### [cancelRunsForEvent()](/sdk/triggerclient/instancemethods/cancel-runs-for-event) + +The `cancelRunsForEvent()` method cancels the job runs (yet to be executed) that are triggered by a given eventId. + #### [getRuns()](/sdk/triggerclient/instancemethods/getruns) The `getRuns()` method gets runs for a Job. diff --git a/packages/core/src/schemas/events.ts b/packages/core/src/schemas/events.ts index f7c980b6d..4f5a0e8d8 100644 --- a/packages/core/src/schemas/events.ts +++ b/packages/core/src/schemas/events.ts @@ -26,3 +26,10 @@ export const GetEventSchema = z.object({ }); export type GetEvent = z.infer; + +export const CancelRunsForEventSchema = z.object({ + cancelled_run_ids: z.array(z.string()), + failed_to_cancel_run_ids: z.array(z.string()), +}); + +export type CancelRunsForEvent = z.infer; diff --git a/packages/trigger-sdk/src/apiClient.ts b/packages/trigger-sdk/src/apiClient.ts index 2f0efc2e7..f46f3384d 100644 --- a/packages/trigger-sdk/src/apiClient.ts +++ b/packages/trigger-sdk/src/apiClient.ts @@ -1,6 +1,7 @@ import { ApiEventLog, ApiEventLogSchema, + CancelRunsForEventSchema, CompleteTaskBodyInput, ConnectionAuthSchema, FailTaskBodyInput, @@ -215,6 +216,26 @@ export class ApiClient { }); } + async cancelRunsForEvent(eventId: string) { + const apiKey = await this.#apiKey(); + + this.#logger.debug("Cancelling runs for event", { + eventId, + }); + + return await zodfetch( + CancelRunsForEventSchema, + `${this.#apiUrl}/api/v1/events/${eventId}/cancel-runs`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + } + ); + } + async updateStatus(runId: string, id: string, status: StatusUpdate) { const apiKey = await this.#apiKey(); diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index bbb5e8b21..4743dd2cc 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -644,6 +644,10 @@ export class TriggerClient { return this.#client.cancelEvent(eventId); } + async cancelRunsForEvent(eventId: string) { + return this.#client.cancelRunsForEvent(eventId); + } + async updateStatus(runId: string, id: string, status: StatusUpdate) { return this.#client.updateStatus(runId, id, status); } From c3564ca0d4517e3e90a4ac522211abde20ce0ec1 Mon Sep 17 00:00:00 2001 From: hmacr Date: Sun, 15 Oct 2023 19:22:33 +0530 Subject: [PATCH 2/6] fix doc --- .../sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx b/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx index 790846a5d..0e56015ef 100644 --- a/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx +++ b/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx @@ -20,7 +20,7 @@ description: "The `cancelRunsForEvent()` instance method will cancel all the job List of Job Run IDs that are cancelled. - List of Job Run IDs that have been failed to be cancelled. + List of Job Run IDs that have failed to be cancelled. From 21f62e780c4fef4a6c19d243762e99aed5be56b6 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 26 Oct 2023 11:30:49 +0100 Subject: [PATCH 3/6] Added tests and refactored response to use camelCase --- .../events/cancelRunsForEvent.server.ts | 4 +- .../instancemethods/cancel-runs-for-event.mdx | 11 ++-- packages/core/src/schemas/events.ts | 4 +- references/job-catalog/src/events.ts | 54 +++++++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts index 8d479b47f..46d1bbe1b 100644 --- a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts +++ b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts @@ -62,8 +62,8 @@ export class CancelRunsForEventService { } return { - cancelled_run_ids: cancelledRunIds, - failed_to_cancel_run_ids: failedToCancelRunIds, + cancelledRunIds: cancelledRunIds, + failedToCancelRunIds: failedToCancelRunIds, }; }); } diff --git a/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx b/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx index 0e56015ef..14f39a038 100644 --- a/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx +++ b/docs/sdk/triggerclient/instancemethods/cancel-runs-for-event.mdx @@ -16,10 +16,10 @@ description: "The `cancelRunsForEvent()` instance method will cancel all the job - + List of Job Run IDs that are cancelled. - + List of Job Run IDs that have failed to be cancelled. @@ -32,10 +32,11 @@ const event = client.sendEvent({ name: "test.job", }); -const res = client.cancelRunsForEvent(event.id); +// Some time later... +const res = await client.cancelRunsForEvent(event.id); -console.log(res.cancelled_run_ids); -console.log(res.failed_to_cancel_run_ids); +console.log(res.cancelledRunIds); +console.log(res.failedToCancelRunIds); ``` diff --git a/packages/core/src/schemas/events.ts b/packages/core/src/schemas/events.ts index 4f5a0e8d8..03b558fe4 100644 --- a/packages/core/src/schemas/events.ts +++ b/packages/core/src/schemas/events.ts @@ -28,8 +28,8 @@ export const GetEventSchema = z.object({ export type GetEvent = z.infer; export const CancelRunsForEventSchema = z.object({ - cancelled_run_ids: z.array(z.string()), - failed_to_cancel_run_ids: z.array(z.string()), + cancelledRunIds: z.array(z.string()), + failedToCancelRunIds: z.array(z.string()), }); export type CancelRunsForEvent = z.infer; diff --git a/references/job-catalog/src/events.ts b/references/job-catalog/src/events.ts index 8e4cb660b..aad942691 100644 --- a/references/job-catalog/src/events.ts +++ b/references/job-catalog/src/events.ts @@ -77,4 +77,58 @@ client.defineJob({ }, }); +client.defineJob({ + id: "cancel-runs-example", + name: "Cancel Runs Example", + version: "1.0.0", + trigger: eventTrigger({ + name: "cancel.runs.example", + }), + run: async (payload, io, ctx) => { + const event = await io.sendEvent("send-event", { + name: "foo.bar", + id: payload.id, + payload: { payload, ctx }, + }); + + await io.wait("wait-1", 1); // 1 second + + await io.runTask("cancel-runs", async () => { + return await client.cancelRunsForEvent(event.id); + }); + }, +}); + +client.defineJob({ + id: "foo-bar-example", + name: "Foo Bar Example", + version: "1.0.0", + trigger: eventTrigger({ + name: "foo.bar", + }), + run: async (payload, io, ctx) => { + await io.logger.info("Hello World", { ctx, payload }); + + await io.wait("wait-1", 10); // 10 seconds + + await io.logger.info("Hello World 2", { ctx, payload }); + }, +}); + +client.defineJob({ + id: "foo-bar-example-2", + name: "Foo Bar Example 2", + version: "1.0.0", + trigger: eventTrigger({ + name: "foo.bar", + }), + run: async (payload, io, ctx) => { + await io.logger.info("Hello World", { ctx, payload }); + + await io.wait("wait-1", 10); // 10 seconds + + await io.logger.info("Hello World 2", { ctx, payload }); + }, +}); + createExpressServer(client); From f8e034c190b37249ed5677f9c73e0394f31b2d5f Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 26 Oct 2023 11:43:23 +0100 Subject: [PATCH 4/6] Fixed import that was causing typecheck to fail --- apps/webapp/app/services/events/cancelRunsForEvent.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts index 46d1bbe1b..dca8b3228 100644 --- a/apps/webapp/app/services/events/cancelRunsForEvent.server.ts +++ b/apps/webapp/app/services/events/cancelRunsForEvent.server.ts @@ -3,7 +3,7 @@ import { AuthenticatedEnvironment } from "../apiAuth.server"; import { JobRunStatus } from "@trigger.dev/database"; import { CancelRunService } from "../runs/cancelRun.server"; import { logger } from "../logger.server"; -import { CancelRunsForEvent } from "@trigger.dev/core/schemas/events"; +import { CancelRunsForEvent } from "@trigger.dev/core"; const CANCELLABLE_JOB_RUN_STATUS: JobRunStatus[] = [ JobRunStatus.PENDING, From c4533c36cb25cef2664391c939d3c6831e7f3495 Mon Sep 17 00:00:00 2001 From: Hemachandar <132386067+hmacr@users.noreply.github.com> Date: Thu, 26 Oct 2023 16:16:09 +0530 Subject: [PATCH 5/6] fix: set error message in runTask & executeJob (#634) * fix: set error message in runTask & executeJob * add changeset --- .changeset/tricky-games-agree.md | 5 +++++ packages/trigger-sdk/src/io.ts | 3 ++- packages/trigger-sdk/src/triggerClient.ts | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/tricky-games-agree.md diff --git a/.changeset/tricky-games-agree.md b/.changeset/tricky-games-agree.md new file mode 100644 index 000000000..4d260ed31 --- /dev/null +++ b/.changeset/tricky-games-agree.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/sdk": patch +--- + +set error messages in runTask and executeJob diff --git a/packages/trigger-sdk/src/io.ts b/packages/trigger-sdk/src/io.ts index de9a77531..a4f8aeb8c 100644 --- a/packages/trigger-sdk/src/io.ts +++ b/packages/trigger-sdk/src/io.ts @@ -818,8 +818,9 @@ export class IO { error: parsedError.data, }); } else { + const message = typeof error === "string" ? error : JSON.stringify(error); await this._apiClient.failTask(this._id, task.id, { - error: { message: JSON.stringify(error), name: "Unknown Error" }, + error: { name: "Unknown error", message }, }); } diff --git a/packages/trigger-sdk/src/triggerClient.ts b/packages/trigger-sdk/src/triggerClient.ts index 4743dd2cc..9ed509d53 100644 --- a/packages/trigger-sdk/src/triggerClient.ts +++ b/packages/trigger-sdk/src/triggerClient.ts @@ -860,9 +860,10 @@ export class TriggerClient { return { status: "ERROR", error: errorWithStack.data }; } + const message = typeof error === "string" ? error : JSON.stringify(error); return { status: "ERROR", - error: { message: "Unknown error" }, + error: { name: "Unknown error", message }, }; } } From f0f04202808c4b311c8a1e64e5247d5504c0891e Mon Sep 17 00:00:00 2001 From: Ronit Panda <72537293+rtpa25@users.noreply.github.com> Date: Thu, 26 Oct 2023 16:20:59 +0530 Subject: [PATCH 6/6] feat: adds helm charts (#636) * feat: adds helm charts template and adds postgres deps * feat: adds all depl (barebones) * feat: adds all depl (barebones) * fix: removes print * fix: image pulling issue * fix: templatisation complete * style: cleanup * feat: works * feat: adds ingress and some docs * fix: ingress bug --> routing to cluster works * feat: completes docs v1 todo --> needs hosting of the helm chart on chosen platform --- .../guides/self-hosting/kubernetes.mdx | 171 ++++++++++++ docs/mint.json | 1 + helm-charts/.gitignore | 1 + helm-charts/.helmignore | 23 ++ helm-charts/Chart.lock | 9 + helm-charts/Chart.yaml | 34 +++ helm-charts/templates/_helpers.tpl | 71 +++++ helm-charts/templates/ingress.yaml | 43 +++ helm-charts/templates/trigger.yaml | 98 +++++++ helm-charts/values.yaml | 248 ++++++++++++++++++ 10 files changed, 699 insertions(+) create mode 100644 docs/documentation/guides/self-hosting/kubernetes.mdx create mode 100644 helm-charts/.gitignore create mode 100644 helm-charts/.helmignore create mode 100644 helm-charts/Chart.lock create mode 100644 helm-charts/Chart.yaml create mode 100644 helm-charts/templates/_helpers.tpl create mode 100644 helm-charts/templates/ingress.yaml create mode 100644 helm-charts/templates/trigger.yaml create mode 100644 helm-charts/values.yaml diff --git a/docs/documentation/guides/self-hosting/kubernetes.mdx b/docs/documentation/guides/self-hosting/kubernetes.mdx new file mode 100644 index 000000000..20982eb66 --- /dev/null +++ b/docs/documentation/guides/self-hosting/kubernetes.mdx @@ -0,0 +1,171 @@ +--- +title: "Deploy to Kubernetes" +description: "Deploy self hosted version of [Trigger.dev](https://trigger.dev) to your kubernetes cluster using our helm chart" +--- +**Prerequisites** +- You have understanding of [Kubernetes](https://kubernetes.io/) +- Installed [Helm package manager](https://helm.sh/) version v3.11.3 or greater +- You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster + +By deploying Trigger.dev on Kubernetes, you can take advantage of its features to ensure that the application is fault-tolerant, highly available, and scalable. +To make the installation process easier and more streamlined, we have created a Helm chart that you can use to install Trigger.dev on Kubernetes. + +Helm is a package manager for Kubernetes that simplifies the installation and management of Kubernetes applications. +With our Helm chart, you can easily install Trigger.dev on Kubernetes, configure it to your liking, and scale it up or down as needed. + +## Install Trigger.dev Helm repository + +```bash +TODO: Add helm repo to artifact hub or cloudsmith +``` + +## Add Helm values + +Create a values.yaml file to configure various installation settings, such as the docker image tags and environment variables. To explore all configurable properties for your values file, [visit this page](https://github.com/triggerdotdev/trigger.dev/tree/main/helm-charts/). + +#### Set image tags + +By default, the application will use the latest tag to retrieve the required Docker images, which may be appropriate for most cases. +However, we recommend that you use a specific version of the Docker image to avoid unexpected changes to the application. + + + To find the latest version number of Trigger.dev, follow the link below + - [Trigger.dev image on github packaes](https://github.com/triggerdotdev/trigger.dev/pkgs/container/trigger.dev) + + +```yaml simple-values-example.yaml +trigger: + name: trigger + replicaCount: 2 + image: + repository: ghcr.io/triggerdotdev/trigger.dev + tag: "latest" # <--- frontend version + pullPolicy: Always +``` + +#### Configure environment variables + +You can configure environment variables for trigger in your Helm values file under the property `envVars`. View configurable [environment variables](../configuration/envars). + +Infisical requires the following backend environment variables to be defined: _`MAGIC_LINK_SECRET`_, _`SESSION_SECRET`_, _`ENCRYPTION_KEY`_, _`DIRECT_URL`_, and _`DATABASE_URL`_ . + +However, when the above environment variables are not defined, the Helm chart +will automatically generate these environment variables for you. The generated environment variables will be saved to a Kubernetes secret and will be preserved between upgrades or uninstalls. + +```yaml simple-values-example.yaml +... +envVars: + ENCRYPTION_KEY: "b1ebe43a6a6e24b2aa8fa0707d3890e3" + MAGIC_LINK_SECRET: "842727396bcee22da68518f959c5730b" + ... +``` +#### Routing external traffic +By default, Trigger.dev takes all traffic coming to your external load balancer's IP address and routes them Trigger.dev's services. +Infisical uses Nginx to route external traffic. You can install Nginx along with Trigger by setting `ingress.enabled` to `true` in the Helm values file. View all [properties for ingress](https://github.com/triggerdotdev/trigger.dev/tree/main/helm-charts/). + +```yaml simple-values-example.yaml +... +ingress: + nginx: + enabled: true #<-- if you would like to install nginx along with Trigger.dev +``` + +#### Database +Trigger.dev uses a SQL database as its persistence layer. With this Helm chart, you spin up a PostgreSQL instance powered by Bitnami along side other Trigger.dev services in your cluster. +When persistence is enabled, the data will be stored as Kubernetes Persistence Volume. View all [properties for postgresql](https://github.com/triggerdotdev/trigger.dev/tree/main/helm-charts/). + +```yaml simple-values-example.yaml +postgresql: + enabled: true + persistence: + enabled: true +``` + +#### Example helm values +```yaml simple-values-example.yaml +trigger: + name: trigger + replicaCount: 2 + image: + repository: ghcr.io/triggerdotdev/trigger.dev + tag: "latest" + pullPolicy: Always + +envVars: + ENCRYPTION_KEY: "b1ebe43a6a6e24b2aa8fa0707d3890e3" + MAGIC_LINK_SECRET: "842727396bcee22da68518f959c5730b" + +ingress: + nginx: + enabled: true #<-- if you would like to install nginx along with Infisical + +``` + + + ```yaml values.yaml + ingress: + nginx: + enabled: true + + trigger: + enabled: true + name: trigger + podAnnotations: {} + deploymentAnnotations: {} + replicaCount: 4 + image: + repository: ghcr.io/triggerdotdev/trigger.dev + tag: "latest" + pullPolicy: IfNotPresent + kubeSecretRef: null + service: + annotations: {} + type: ClusterIP + nodePort: "" + + # View all environment variables TODO: Docs for all env vars + envVars: + DATABASE_URL: <> + DIRECT_URL: <> + ENCRYPTION_KEY: <> + + + ## Postgresql DB persistence + postgresql: + enabled: true + persistence: + enabled: true + + ingress: + enabled: true + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" # <-- if you are setting up HTTPS + hostName: app.yourdomain.com ## <- Replace with your own domain + trigger: + path: / + pathType: Prefix + tls: # <-- if you are setting up HTTPS + - secretName: echo-tls + hosts: + - app.yourdomain.com + + ``` + + +## Install the Helm chart + +By default, the helm chart will be installed on your default namespace. If you wish to install the Chart on a different namespace, you may specify +that by adding the `--namespace ` to your `helm install` command. + +```bash +## Installs to default namespace +TODO: not published +``` + +## Access Trigger.dev +Allow 3-5 minutes for the deployment to complete. Once done, you should now be able to access Trigger.dev on the IP address exposed via Ingress on your load balancer. If you are not sure what the IP address is run `kubectl get ingress` to view the external IP address exposing Trigger.dev. + + +Once installation is complete, you will have to create the first account. No default account is provided. + + diff --git a/docs/mint.json b/docs/mint.json index ef5e50bca..98ba3365e 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -206,6 +206,7 @@ "documentation/guides/self-hosting", "documentation/guides/self-hosting/flyio", "documentation/guides/self-hosting/render", + "documentation/guides/self-hosting/kubernetes", "documentation/guides/self-hosting/supabase", "documentation/guides/tunneling-platform" ] diff --git a/helm-charts/.gitignore b/helm-charts/.gitignore new file mode 100644 index 000000000..711a39c54 --- /dev/null +++ b/helm-charts/.gitignore @@ -0,0 +1 @@ +charts/ \ No newline at end of file diff --git a/helm-charts/.helmignore b/helm-charts/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm-charts/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm-charts/Chart.lock b/helm-charts/Chart.lock new file mode 100644 index 000000000..25db8abe6 --- /dev/null +++ b/helm-charts/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 13.1.5 +- name: ingress-nginx + repository: https://kubernetes.github.io/ingress-nginx + version: 4.0.13 +digest: sha256:e439e4b30ba18357defec97ba080973743a4724c423b78913990409f78f1ebd8 +generated: "2023-10-20T14:22:57.044126+05:30" diff --git a/helm-charts/Chart.yaml b/helm-charts/Chart.yaml new file mode 100644 index 000000000..04ff9e562 --- /dev/null +++ b/helm-charts/Chart.yaml @@ -0,0 +1,34 @@ +apiVersion: v2 +name: trigger +description: A Helm chart for a full Trigger application stack + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" + +dependencies: + - name: postgresql + version: "~13.1.5" + repository: https://charts.bitnami.com/bitnami + condition: postgresql.enabled + - name: ingress-nginx + version: 4.0.13 + repository: https://kubernetes.github.io/ingress-nginx + condition: ingress.nginx.enabled diff --git a/helm-charts/templates/_helpers.tpl b/helm-charts/templates/_helpers.tpl new file mode 100644 index 000000000..5d060c433 --- /dev/null +++ b/helm-charts/templates/_helpers.tpl @@ -0,0 +1,71 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "trigger.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "trigger.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create unified labels for trigger components +*/}} +{{- define "trigger.common.matchLabels" -}} +app: {{ template "trigger.name" . }} +release: {{ .Release.Name }} +{{- end -}} + +{{- define "trigger.common.metaLabels" -}} +chart: {{ template "trigger.chart" . }} +heritage: {{ .Release.Service }} +{{- end -}} + +{{- define "trigger.common.labels" -}} +{{ include "trigger.common.matchLabels" . }} +{{ include "trigger.common.metaLabels" . }} +{{- end -}} + +{{- define "trigger.labels" -}} +{{ include "trigger.matchLabels" . }} +{{ include "trigger.common.metaLabels" . }} +{{- end -}} + +{{- define "trigger.matchLabels" -}} +component: {{ .Values.trigger.name | quote }} +{{ include "trigger.common.matchLabels" . }} +{{- end -}} + +{{/* +Create a fully qualified postgresql name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "trigger.postgresql.hostname" -}} +{{- if .Values.postgresql.fullnameOverride -}} +{{- .Values.postgresql.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name .Values.postgresql.name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s-%s" .Release.Name $name .Values.postgresql.name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create the postgresql connection string. +*/}} +{{- define "trigger.postgresql.connectionString" -}} +{{- $host := include "trigger.postgresql.hostname" . -}} +{{- $port := 5432 -}} +{{- $username := .Values.postgresql.global.postgresql.postgresqlUsername | default "postgres" -}} +{{- $password := .Values.postgresql.global.postgresql.postgresqlPassword | default "password" -}} +{{- $database := .Values.postgresql.global.postgresql.postgresqlDatabase | default "trigger" -}} +{{- $connectionString := printf "postgresql://%s:%s@%s:%d/%s" $username $password $host $port $database -}} +{{- printf "%s" $connectionString -}} +{{- end -}} \ No newline at end of file diff --git a/helm-charts/templates/ingress.yaml b/helm-charts/templates/ingress.yaml new file mode 100644 index 000000000..3f4e73f6e --- /dev/null +++ b/helm-charts/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{ if .Values.ingress.enabled }} +{{- $ingress := .Values.ingress }} +{{- if and $ingress.ingressClassName (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey $ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set $ingress.annotations "kubernetes.io/ingress.class" $ingress.ingressClassName}} + {{- end }} +{{- end }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: trigger-ingress + {{- with $ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and $ingress.ingressClassName (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ $ingress.ingressClassName | default "nginx" }} + {{- end }} +{{- if $ingress.tls }} + tls: + {{- range $ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + - http: + paths: + - path: {{ $ingress.trigger.path }} + pathType: {{ $ingress.trigger.pathType }} + backend: + service: + name: {{ include "trigger.name" . }} + port: + number: 3000 + {{- if $ingress.hostName }} + host: {{ $ingress.hostName }} + {{- end }} +{{ end }} \ No newline at end of file diff --git a/helm-charts/templates/trigger.yaml b/helm-charts/templates/trigger.yaml new file mode 100644 index 000000000..03900e658 --- /dev/null +++ b/helm-charts/templates/trigger.yaml @@ -0,0 +1,98 @@ +{{- $trigger := .Values.trigger -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "trigger.name" . }} + annotations: + updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }} + {{- with $trigger.deploymentAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + labels: + {{- include "trigger.labels" . | nindent 4 }} +spec: + replicas: {{ $trigger.replicaCount }} + selector: + matchLabels: + {{- include "trigger.matchLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "trigger.matchLabels" . | nindent 8 }} + annotations: + updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }} + {{- with $trigger.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with $trigger.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ $trigger.name }} + image: "{{ $trigger.image.repository }}:{{ $trigger.image.tag | default "latest" }}" + imagePullPolicy: {{ $trigger.image.pullPolicy }} + ports: + - name: http + containerPort: 3000 + protocol: TCP + readinessProbe: + httpGet: + path: / + port: 3000 + envFrom: + - secretRef: + name: {{ $trigger.kubeSecretRef | default (include "trigger.name" .) }} + {{- if $trigger.resources }} + resources: {{- toYaml $trigger.resources | nindent 12 }} + {{- end }} +--- + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "trigger.name" . }} + labels: + {{- include "trigger.labels" . | nindent 4 }} + {{- with $trigger.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ $trigger.service.type }} + selector: + {{- include "trigger.matchLabels" . | nindent 8 }} + ports: + - port: 3000 + targetPort: 3000 + protocol: TCP + {{- if eq $trigger.service.type "NodePort" }} + nodePort: {{ $trigger.service.nodePort }} + {{- end }} + +--- + +{{ if not $trigger.kubeSecretRef }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "trigger.name" . }} + annotations: + "helm.sh/resource-policy": "keep" +type: Opaque +stringData: + {{- $requiredVars := dict "MAGIC_LINK_SECRET" (randAlphaNum 32 | lower) + "SESSION_SECRET" (randAlphaNum 32 | lower) + "ENCRYPTION_KEY" (randAlphaNum 32 | lower) + "DIRECT_URL" (include "trigger.postgresql.connectionString" .) + "DATABASE_URL" (include "trigger.postgresql.connectionString" .) }} + {{- $secretObj := (lookup "v1" "Secret" .Release.Namespace (include "trigger.name" .)) | default dict }} + {{- $secretData := (get $secretObj "data") | default dict }} + {{ range $key, $value := .Values.envVars }} + {{- $default := get $requiredVars $key -}} + {{- $current := get $secretData $key | b64dec -}} + {{- $v := $value | default ($current | default $default) -}} + {{ $key }}: {{ $v | quote }} + {{ end -}} +{{- end }} \ No newline at end of file diff --git a/helm-charts/values.yaml b/helm-charts/values.yaml new file mode 100644 index 000000000..0a62e8718 --- /dev/null +++ b/helm-charts/values.yaml @@ -0,0 +1,248 @@ +# Default values for helm-charts. +# This is a YAML-formatted file. +## @section Common parameters +## + +## @param nameOverride Override release name +## +nameOverride: "" +## @param fullnameOverride Override release fullname +## +fullnameOverride: "" + +## @section trigger -- main app +## +trigger: + ## @param trigger.name + name: trigger + ## @param trigger.fullnameOverride trigger fullnameOverride + ## + fullnameOverride: "" + ## @param trigger.podAnnotations trigger pod annotations + ## + podAnnotations: {} + ## @param trigger.deploymentAnnotations trigger deployment annotations + ## + deploymentAnnotations: {} + ## @param trigger.replicaCount trigger replica count + ## + replicaCount: 2 + ## trigger image parameters + ## + image: + ## @param trigger.image.repository trigger image repository + ## + repository: ghcr.io/triggerdotdev/trigger.dev + ## @param trigger.image.tag trigger image tag + ## + tag: "latest" + ## @param trigger.image.pullPolicy trigger image pullPolicy + ## + pullPolicy: Always + ## @param trigger.resources.limits.memory container memory limit [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) + ## @param trigger.resources.requests.cpu container CPU request [check the offical kubernetes documentations](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) + ## + resources: + limits: + memory: 800Mi + requests: + cpu: 250m + ## @param trigger.affinity Backend pod affinity + ## + affinity: {} + ## @param trigger.kubeSecretRef trigger secret resource reference name + ## + kubeSecretRef: "" + ## trigger service + ## + service: + ## @param trigger.service.annotations trigger service annotations + ## + annotations: {} + ## @param trigger.service.type trigger service type + ## + type: ClusterIP + ## @param trigger.service.nodePort trigger service nodePort (used if above type is `NodePort`) + ## + nodePort: "" + +## trigger environment variables configuration +envVars: + ENCRYPTION_KEY: "" + MAGIC_LINK_SECRET: "" + SESSION_SECRET: "" + LOGIN_ORIGIN: "" + APP_ORIGIN: "" + DIRECT_URL: "" + DATABASE_URL: "" + FROM_EMAIL: "" + REPLY_TO_EMAIL: "" + RESEND_API_KEY: "" + AUTH_GITHUB_CLIENT_ID: "" + AUTH_GITHUB_CLIENT_SECRET: "" + +## @section Postgresql(®) parameters +## Documentation : https://github.com/bitnami/charts/tree/main/bitnami/postgresql-ha +## +postgresql: + ## @param postgresql.enabled Enable Postgresql(®) + ## + enabled: true + ## @param postgresql.name Name used to build variables (deprecated) + ## + name: "postgresql" + ## @param postgresql.nameOverride Name override + ## + nameOverride: "postgresql" + ## @param fullnameOverride String to fully override common.names.fullname template + ## + fullnameOverride: "postgresql" + + global: + postgresql: + ## @param global.postgresql.auth.postgresPassword Password for the "postgres" admin user (overrides `auth.postgresPassword`) + ## @param global.postgresql.auth.username Name for a custom user to create (overrides `auth.username`) + ## @param global.postgresql.auth.password Password for the custom user to create (overrides `auth.password`) + ## @param global.postgresql.auth.database Name for a custom database to create (overrides `auth.database`) + ## @param global.postgresql.auth.existingSecret Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). + ## @param global.postgresql.auth.secretKeys.adminPasswordKey Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + ## @param global.postgresql.auth.secretKeys.userPasswordKey Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + ## @param global.postgresql.auth.secretKeys.replicationPasswordKey Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.replicationPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + ## + auth: + postgresPassword: "password" + username: "postgres" + password: "password" + database: "trigger" + ## @param global.postgresql.service.ports.postgresql PostgreSQL service port (overrides `service.ports.postgresql`) + ## + service: + ports: + postgresql: "5432" + + ## Bitnami PostgreSQL image version + ## ref: https://hub.docker.com/r/bitnami/postgresql/tags/ + ## @param image.registry PostgreSQL image registry + ## @param image.repository PostgreSQL image repository + ## @param image.tag PostgreSQL image tag (immutable tags are recommended) + ## @param image.digest PostgreSQL image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag + ## @param image.pullPolicy PostgreSQL image pull policy + ## @param image.pullSecrets Specify image pull secrets + ## @param image.debug Specify if debug values should be set + ## + image: + registry: docker.io + repository: bitnami/postgresql + tag: 16.0.0-debian-11-r13 + + architecture: standalone + ## Replication configuration + ## Ignored if `architecture` is `standalone` + ## + ## @param containerPorts.postgresql PostgreSQL container port + ## + containerPorts: + postgresql: 5432 + + postgresqlDataDir: /bitnami/postgresql/data + ## @param postgresqlSharedPreloadLibraries Shared preload libraries (comma-separated list) + ## + postgresqlSharedPreloadLibraries: "pgaudit" + ## @section PostgreSQL Primary parameters + ## + primary: + ## Configure extra options for PostgreSQL Primary containers' liveness, readiness and startup probes + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes + ## @param primary.livenessProbe.enabled Enable livenessProbe on PostgreSQL Primary containers + ## @param primary.livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe + ## @param primary.livenessProbe.periodSeconds Period seconds for livenessProbe + ## @param primary.livenessProbe.timeoutSeconds Timeout seconds for livenessProbe + ## @param primary.livenessProbe.failureThreshold Failure threshold for livenessProbe + ## @param primary.livenessProbe.successThreshold Success threshold for livenessProbe + ## + livenessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + ## @param primary.readinessProbe.enabled Enable readinessProbe on PostgreSQL Primary containers + ## @param primary.readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe + ## @param primary.readinessProbe.periodSeconds Period seconds for readinessProbe + ## @param primary.readinessProbe.timeoutSeconds Timeout seconds for readinessProbe + ## @param primary.readinessProbe.failureThreshold Failure threshold for readinessProbe + ## @param primary.readinessProbe.successThreshold Success threshold for readinessProbe + ## + readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + ## @param primary.startupProbe.enabled Enable startupProbe on PostgreSQL Primary containers + ## @param primary.startupProbe.initialDelaySeconds Initial delay seconds for startupProbe + ## @param primary.startupProbe.periodSeconds Period seconds for startupProbe + ## @param primary.startupProbe.timeoutSeconds Timeout seconds for startupProbe + ## @param primary.startupProbe.failureThreshold Failure threshold for startupProbe + ## @param primary.startupProbe.successThreshold Success threshold for startupProbe + ## + startupProbe: + enabled: false + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 15 + successThreshold: 1 + persistence: + ## @param primary.persistence.enabled Enable PostgreSQL Primary data persistence using PVC + ## + enabled: true + ## @param primary.persistence.existingClaim Name of an existing PVC to use + ## + existingClaim: "" + ## @param primary.persistence.accessModes PVC Access Mode for PostgreSQL volume + ## + accessModes: + - ReadWriteOnce + ## @param primary.persistence.size PVC Storage Request for PostgreSQL volume + ## + size: 8Gi + +## @section Ingress parameters +## +ingress: + ## @param ingress.enabled Enable ingress + ## + enabled: true + ## @param ingress.ingressClassName Ingress class name + ## + ingressClassName: nginx + ## @param ingress.nginx.enabled Ingress controller + ## + nginx: + enabled: false + ## @param ingress.annotations Ingress annotations + ## + annotations: + {} + # kubernetes.io/ingress.class: "nginx" + # cert-manager.io/issuer: letsencrypt-nginx + ## @param ingress.hostName Ingress hostname (your custom domain name, e.g. `infisical.example.org`) + ## Replace with your own domain + ## + hostName: "" + ## @skip ingress.frontend + ## + trigger: + path: / + pathType: Prefix + ## @param ingress.tls Ingress TLS hosts (matching above hostName) + ## Replace with your own domain + ## + tls: + [] + # - secretName: letsencrypt-nginx + # hosts: + # - infisical.local