Skip to content

Commit a5b6f06

Browse files
committed
Merge remote-tracking branch 'origin/main' into run-engine-2-retry-oom
# Conflicts: # packages/core/package.json
2 parents 206b9bc + e297c7f commit a5b6f06

File tree

31 files changed

+2503
-56
lines changed

31 files changed

+2503
-56
lines changed

.dockerignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
# dependencies
1111

12-
node_modules
12+
**/node_modules
1313
.pnp
1414
.pnp.js
1515

.github/actions/get-image-tag/action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ runs:
3131
sha=$(echo ${{ github.sha }} | head -c7)
3232
ts=$(date +%s)
3333
tag=${env}-${sha}-${ts}
34+
elif [[ "${{ github.ref_name }}" == re2-*-* ]]; then
35+
env=$(echo ${{ github.ref_name }} | cut -d- -f2)
36+
sha=$(echo ${{ github.sha }} | head -c7)
37+
ts=$(date +%s)
38+
tag=${env}-${sha}-${ts}
3439
elif [[ "${{ github.ref_name }}" == v.docker.* ]]; then
3540
version="${GITHUB_REF_NAME#v.docker.}"
3641
tag="v${version}"
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: "⚒️ Publish Worker RE2"
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
image_tag:
7+
description: The image tag to publish
8+
type: string
9+
required: false
10+
default: ""
11+
push:
12+
tags:
13+
- "re2-test-*"
14+
- "re2-prod-*"
15+
16+
permissions:
17+
packages: write
18+
contents: read
19+
20+
jobs:
21+
check-branch:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Fail if re2-prod-* is pushed from a non-main branch
25+
if: startsWith(github.ref_name, 're2-prod-') && github.base_ref != 'main'
26+
run: |
27+
echo "🚫 re2-prod-* tags can only be pushed from the main branch."
28+
exit 1
29+
build:
30+
needs: check-branch
31+
strategy:
32+
matrix:
33+
package: [supervisor]
34+
runs-on: ubuntu-latest
35+
env:
36+
DOCKER_BUILDKIT: "1"
37+
steps:
38+
- name: ⬇️ Checkout git repo
39+
uses: actions/checkout@v4
40+
41+
- name: 📦 Get image repo
42+
id: get_repository
43+
run: |
44+
if [[ "${{ matrix.package }}" == *-provider ]]; then
45+
provider_type=$(echo "${{ matrix.package }}" | cut -d- -f1)
46+
repo=provider/${provider_type}
47+
else
48+
repo="${{ matrix.package }}"
49+
fi
50+
echo "repo=${repo}" >> "$GITHUB_OUTPUT"
51+
52+
- id: get_tag
53+
uses: ./.github/actions/get-image-tag
54+
with:
55+
tag: ${{ inputs.image_tag }}
56+
57+
- name: 🐋 Set up Docker Buildx
58+
uses: docker/setup-buildx-action@v3
59+
60+
# ..to avoid rate limits when pulling images
61+
- name: 🐳 Login to DockerHub
62+
uses: docker/login-action@v3
63+
with:
64+
username: ${{ secrets.DOCKERHUB_USERNAME }}
65+
password: ${{ secrets.DOCKERHUB_TOKEN }}
66+
67+
- name: 🚢 Build Container Image
68+
run: |
69+
docker build -t infra_image -f ./apps/${{ matrix.package }}/Containerfile .
70+
71+
# ..to push image
72+
- name: 🐙 Login to GitHub Container Registry
73+
uses: docker/login-action@v3
74+
with:
75+
registry: ghcr.io
76+
username: ${{ github.repository_owner }}
77+
password: ${{ secrets.GITHUB_TOKEN }}
78+
79+
- name: 🐙 Push to GitHub Container Registry
80+
run: |
81+
docker tag infra_image "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
82+
docker push "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
83+
env:
84+
REGISTRY: ghcr.io/triggerdotdev
85+
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
86+
IMAGE_TAG: ${{ steps.get_tag.outputs.tag }}
87+
88+
- name: 🐙 Push 'v3' tag to GitHub Container Registry
89+
if: steps.get_tag.outputs.is_semver == 'true'
90+
run: |
91+
docker tag infra_image "$REGISTRY/$REPOSITORY:v3"
92+
docker push "$REGISTRY/$REPOSITORY:v3"
93+
env:
94+
REGISTRY: ghcr.io/triggerdotdev
95+
REPOSITORY: ${{ steps.get_repository.outputs.repo }}

apps/coordinator/src/checkpointer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ExponentialBackoff } from "@trigger.dev/core/v3/apps";
2-
import { testDockerCheckpoint } from "@trigger.dev/core/v3/checkpoints";
2+
import { testDockerCheckpoint } from "@trigger.dev/core/v3/serverOnly";
33
import { nanoid } from "nanoid";
44
import fs from "node:fs/promises";
55
import { ChaosMonkey } from "./chaosMonkey";

apps/supervisor/.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# This needs to match the token of the worker group you want to connect to
2+
TRIGGER_WORKER_TOKEN=
3+
4+
# This needs to match the MANAGED_WORKER_SECRET env var on the webapp
5+
MANAGED_WORKER_SECRET=managed-secret
6+
7+
# Point this at the webapp in prod
8+
TRIGGER_API_URL=http://localhost:3030
9+
10+
# Point this at the OTel collector in prod
11+
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3030/otel
12+
# Use this on macOS
13+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://host.docker.internal:3030/otel
14+
15+
# Optional settings
16+
DEBUG=1
17+
ENFORCE_MACHINE_PRESETS=1
18+
TRIGGER_DEQUEUE_INTERVAL_MS=1000

apps/supervisor/.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v22.12.0

apps/supervisor/Containerfile

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
FROM node:22-alpine@sha256:9bef0ef1e268f60627da9ba7d7605e8831d5b56ad07487d24d1aa386336d1944 AS node-22-alpine
2+
3+
WORKDIR /app
4+
5+
FROM node-22-alpine AS pruner
6+
7+
COPY --chown=node:node . .
8+
RUN npx -q [email protected] prune --scope=supervisor --docker
9+
RUN find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
10+
11+
FROM node-22-alpine AS base
12+
13+
RUN apk add --no-cache dumb-init
14+
15+
COPY --chown=node:node .gitignore .gitignore
16+
COPY --from=pruner --chown=node:node /app/out/json/ .
17+
COPY --from=pruner --chown=node:node /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
18+
COPY --from=pruner --chown=node:node /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
19+
20+
FROM base AS dev-deps
21+
RUN corepack enable
22+
ENV NODE_ENV development
23+
24+
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --no-frozen-lockfile
25+
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --ignore-scripts --no-frozen-lockfile
26+
27+
FROM base AS prod-deps
28+
RUN corepack enable
29+
ENV NODE_ENV production
30+
31+
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --prod --no-frozen-lockfile
32+
33+
COPY --from=pruner --chown=node:node /app/internal-packages/database/prisma/schema.prisma /app/internal-packages/database/prisma/schema.prisma
34+
35+
ENV NPM_CONFIG_IGNORE_WORKSPACE_ROOT_CHECK true
36+
RUN pnpx [email protected] generate --schema /app/internal-packages/database/prisma/schema.prisma
37+
38+
FROM base AS builder
39+
RUN corepack enable
40+
41+
COPY --from=pruner --chown=node:node /app/out/full/ .
42+
COPY --from=dev-deps --chown=node:node /app/ .
43+
COPY --chown=node:node turbo.json turbo.json
44+
COPY --chown=node:node .configs/tsconfig.base.json .configs/tsconfig.base.json
45+
COPY --chown=node:node scripts/updateVersion.ts scripts/updateVersion.ts
46+
47+
RUN pnpm run generate && \
48+
pnpm run -r --filter supervisor... build
49+
50+
FROM base AS runner
51+
52+
RUN corepack enable
53+
ENV NODE_ENV production
54+
55+
COPY --from=pruner --chown=node:node /app/out/full/ .
56+
COPY --from=prod-deps --chown=node:node /app .
57+
COPY --from=builder --chown=node:node /app/apps/supervisor ./apps/supervisor
58+
59+
EXPOSE 8000
60+
61+
USER node
62+
63+
CMD [ "/usr/bin/dumb-init", "--", "pnpm", "run", "--filter", "supervisor", "start"]

apps/supervisor/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Supervisor
2+
3+
## Dev setup
4+
5+
1. Create a worker group
6+
7+
```sh
8+
api_url=http://localhost:3030
9+
wg_name=my-worker
10+
11+
# edit these
12+
admin_pat=tr_pat_...
13+
project_id=clsw6q8wz...
14+
15+
curl -sS \
16+
-X POST \
17+
"$api_url/admin/api/v1/workers" \
18+
-H "Authorization: Bearer $admin_pat" \
19+
-H "Content-Type: application/json" \
20+
-d "{
21+
\"name\": \"$wg_name\",
22+
\"makeDefault\": true,
23+
\"projectId\": \"$project_id\"
24+
}"
25+
```
26+
27+
2. Create `.env` and set the worker token
28+
29+
```sh
30+
cp .env.example .env
31+
32+
# Then edit your .env and set this to the token.plaintext value
33+
TRIGGER_WORKER_TOKEN=tr_wgt_...
34+
```
35+
36+
3. Start the supervisor
37+
38+
```sh
39+
pnpm dev
40+
```
41+
42+
4. Build CLI, then deploy a reference project
43+
44+
```sh
45+
pnpm exec trigger deploy --self-hosted
46+
47+
# The additional network flag is required on linux
48+
pnpm exec trigger deploy --self-hosted --network host
49+
```

apps/supervisor/package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "supervisor",
3+
"private": true,
4+
"version": "0.0.1",
5+
"main": "dist/index.js",
6+
"type": "module",
7+
"scripts": {
8+
"build": "tsc",
9+
"dev": "tsx --experimental-sqlite --require dotenv/config --watch src/index.ts",
10+
"start": "node --experimental-sqlite dist/index.js",
11+
"typecheck": "tsc --noEmit"
12+
},
13+
"dependencies": {
14+
"@kubernetes/client-node": "^1.0.0",
15+
"@trigger.dev/core": "workspace:*",
16+
"dockerode": "^4.0.3",
17+
"nanoid": "^5.0.9",
18+
"socket.io": "4.7.4",
19+
"std-env": "^3.8.0",
20+
"tinyexec": "^0.3.1",
21+
"zod": "3.23.8"
22+
},
23+
"devDependencies": {
24+
"@types/dockerode": "^3.3.33",
25+
"docker-api-ts": "^0.2.2"
26+
}
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as k8s from "@kubernetes/client-node";
2+
import { assertExhaustive } from "@trigger.dev/core/utils";
3+
4+
export const RUNTIME_ENV = process.env.KUBERNETES_PORT ? "kubernetes" : "local";
5+
6+
export function createK8sApi() {
7+
const kubeConfig = getKubeConfig();
8+
9+
const api = {
10+
core: kubeConfig.makeApiClient(k8s.CoreV1Api),
11+
batch: kubeConfig.makeApiClient(k8s.BatchV1Api),
12+
apps: kubeConfig.makeApiClient(k8s.AppsV1Api),
13+
};
14+
15+
return api;
16+
}
17+
18+
export type K8sApi = ReturnType<typeof createK8sApi>;
19+
20+
function getKubeConfig() {
21+
console.log("getKubeConfig()", { RUNTIME_ENV });
22+
23+
const kubeConfig = new k8s.KubeConfig();
24+
25+
switch (RUNTIME_ENV) {
26+
case "local":
27+
kubeConfig.loadFromDefault();
28+
break;
29+
case "kubernetes":
30+
kubeConfig.loadFromCluster();
31+
break;
32+
default:
33+
assertExhaustive(RUNTIME_ENV);
34+
}
35+
36+
return kubeConfig;
37+
}
38+
39+
export { k8s };

apps/supervisor/src/env.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { randomUUID } from "crypto";
2+
import { env as stdEnv } from "std-env";
3+
import { z } from "zod";
4+
import { getDockerHostDomain } from "./util.js";
5+
6+
const Env = z.object({
7+
// This will come from `status.hostIP` in k8s
8+
WORKER_HOST_IP: z.string().default(getDockerHostDomain()),
9+
TRIGGER_API_URL: z.string().url(),
10+
TRIGGER_WORKER_TOKEN: z.string(),
11+
// This will come from `spec.nodeName` in k8s
12+
TRIGGER_WORKER_INSTANCE_NAME: z.string().default(randomUUID()),
13+
MANAGED_WORKER_SECRET: z.string(),
14+
TRIGGER_WORKLOAD_API_PORT: z.coerce.number().default(8020),
15+
TRIGGER_WORKLOAD_API_PORT_EXTERNAL: z.coerce.number().default(8020),
16+
TRIGGER_WARM_START_URL: z.string().optional(),
17+
TRIGGER_CHECKPOINT_URL: z.string().optional(),
18+
TRIGGER_DEQUEUE_INTERVAL_MS: z.coerce.number().int().default(1000),
19+
20+
// Used by the workload manager, e.g docker/k8s
21+
DOCKER_NETWORK: z.string().default("host"),
22+
OTEL_EXPORTER_OTLP_ENDPOINT: z.string().url(),
23+
ENFORCE_MACHINE_PRESETS: z.coerce.boolean().default(false),
24+
25+
// Used by the resource monitor
26+
OVERRIDE_CPU_TOTAL: z.coerce.number().optional(),
27+
OVERRIDE_MEMORY_TOTAL_GB: z.coerce.number().optional(),
28+
});
29+
30+
export const env = Env.parse(stdEnv);

0 commit comments

Comments
 (0)