Sitelet https://github.com/NodeOps-app/createos-sandbox-sdk/commit/d2b0ce786c106578c24d4d28c9ab5978a87ba29b
Skip to content

Commit d2b0ce7

Browse files
committed
ci: shift to createos sandbox
1 parent 8aa447d commit d2b0ce7

1 file changed

Lines changed: 46 additions & 90 deletions

File tree

‎.github/workflows/examples.yml‎

Lines changed: 46 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,28 @@ name: examples
55
# it, and destroys it in a `finally` block — so a green job means the example
66
# worked against production.
77
#
8-
# Cost note: every run spawns real VMs and makes paid LLM API calls.
8+
# Runner: our own createos self-hosted runner (createos-sandbox-ghar). Every job
9+
# runs on `runs-on: [createos]`, so the controller boots a throwaway createos
10+
# microVM per job — the CI itself runs inside a sandbox. One example per matrix
11+
# job, one microVM per example; the runner autoscaler handles concurrency, so
12+
# there is no manual sharding here.
13+
#
14+
# Reaper constraint: ghar destroys any runner VM older than REAPER_MAX_AGE_MS
15+
# (30 min in prod). Each job must finish well under that, hence one example per
16+
# job + a job `timeout-minutes` below the cutoff — never batch examples serially
17+
# into a single job or the reaper kills the VM mid-run.
18+
#
19+
# Cost note: every run spawns real VMs and makes paid LLM API calls, and 42
20+
# example jobs boot 42 microVMs at once against a shared MAX_CONCURRENT pool.
921
# Triggers are kept deliberate: nightly, manual, and on push to main.
1022
#
1123
# No `pull_request` trigger by design. Examples run arbitrary `bun index.ts`
12-
# with real provider secrets on a self-hosted runner; firing that on PRs
13-
# would let any fork PR exfiltrate those secrets. Examples are verified on
14-
# push to main (post-merge) instead.
24+
# with real provider secrets; firing that on PRs would let any fork PR
25+
# exfiltrate those secrets. Examples are verified on push to main (post-merge)
26+
# instead.
1527

1628
on:
1729
workflow_dispatch:
18-
inputs:
19-
parallelism:
20-
description: "Number of shards to split examples across (each shard is its own runner pod)"
21-
type: string
22-
default: "10"
2330
schedule:
2431
- cron: "17 3 * * *" # nightly 03:17 UTC
2532
push:
@@ -40,15 +47,13 @@ jobs:
4047
# Build the example matrix from the examples/ directory so new examples are
4148
# picked up automatically — no edits to this file when one is added.
4249
# Discover also builds the SDK and installs example deps ONCE, seeding a
43-
# shared cache. The matrix jobs restore dist/ + examples/node_modules from
44-
# it instead of repeating `bun install` + `bun run build` 37 times.
50+
# shared cache. The matrix jobs restore dist/ + the bun store from it instead
51+
# of repeating `bun install` + `bun run build` 42 times.
4552
discover:
46-
runs-on: arc-runner-set
53+
runs-on: [createos]
4754
outputs:
4855
examples: ${{ steps.list.outputs.examples }}
4956
cache-key: ${{ steps.key.outputs.key }}
50-
shards: ${{ steps.shards.outputs.shards }}
51-
shard-count: ${{ steps.shards.outputs.shard-count }}
5257
steps:
5358
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5459
- id: list
@@ -63,18 +68,6 @@ jobs:
6368
run: |
6469
echo "key=examples-deps-${{ hashFiles('src/**', 'tsconfig.json', 'bun.lock', 'examples/bun.lock', 'examples/package.json') }}" >> "$GITHUB_OUTPUT"
6570
66-
# Parallelism is decided here, not hardcoded in the run job: N shards ->
67-
# N independent runner pods in the matrix below, each handling its own
68-
# slice of the example set instead of stacking concurrent `bun index.ts`
69-
# processes on a single pod.
70-
- id: shards
71-
env:
72-
PARALLELISM: ${{ github.event.inputs.parallelism }}
73-
run: |
74-
n="${PARALLELISM:-10}"
75-
echo "shard-count=$n" >> "$GITHUB_OUTPUT"
76-
echo "shards=$(jq -nc --argjson n "$n" '[range($n)]')" >> "$GITHUB_OUTPUT"
77-
7871
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
7972
with:
8073
bun-version: latest
@@ -105,18 +98,16 @@ jobs:
10598
10699
run:
107100
needs: discover
108-
runs-on: arc-runner-set
101+
runs-on: [createos]
109102
strategy:
110103
fail-fast: false
111104
matrix:
112-
shard: ${{ fromJson(needs.discover.outputs.shards) }}
113-
# worst case: ceil(examples / shard-count) examples run serially per shard,
114-
# 20 min timeout each (42 examples / 10 shards = 5 x 20 min)
115-
timeout-minutes: 120
105+
example: ${{ fromJson(needs.discover.outputs.examples) }}
106+
# One example per job; must stay under the ghar reaper's 30-min VM cutoff.
107+
# The example itself is capped at `timeout 1200` (20 min) below, leaving
108+
# setup headroom before this job timeout.
109+
timeout-minutes: 25
116110
env:
117-
EXAMPLES_JSON: ${{ needs.discover.outputs.examples }}
118-
SHARD: ${{ matrix.shard }}
119-
SHARD_COUNT: ${{ needs.discover.outputs.shard-count }}
120111
# createos-sandbox control plane (read by the SDK + most examples)
121112
CREATEOS_SANDBOX_API_KEY: ${{ secrets.CREATEOS_SANDBOX_API_KEY }}
122113
CREATEOS_SANDBOX_BASE_URL: ${{ secrets.CREATEOS_SANDBOX_BASE_URL }}
@@ -173,72 +164,37 @@ jobs:
173164
working-directory: examples
174165
run: bun install
175166

176-
# example 20 needs python3-venv; install once since the job runs all examples
177-
- name: Set up Python
167+
# example 20 needs python3-venv; only that job pays for the setup.
168+
- name: Set up Python (example 20)
169+
if: startsWith(matrix.example, '20-')
178170
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
179171
with:
180172
python-version: "3.12"
181173

182-
# example 38 mounts an S3 bucket; play.min.io purges buckets periodically
174+
# example 38 mounts an S3 bucket; play.min.io purges buckets periodically.
183175
- name: Ensure S3 bucket (example 38)
176+
if: startsWith(matrix.example, '38-')
184177
run: |
185178
curl -sSfL https://dl.min.io/client/mc/release/linux-amd64/mc -o /tmp/mc
186179
chmod +x /tmp/mc
187180
/tmp/mc alias set ci "$S3_ENDPOINT" "$S3_ACCESS_KEY" "$S3_SECRET_KEY"
188181
/tmp/mc mb --ignore-existing "ci/$S3_BUCKET"
189182
190-
- name: Run examples (shard ${{ matrix.shard }}/${{ needs.discover.outputs.shard-count }})
191-
run: |
192-
mkdir -p /tmp/example-logs /tmp/example-fails
193-
mapfile -t ALL < <(echo "$EXAMPLES_JSON" | jq -r '.[]')
194-
EXAMPLES=()
195-
for i in "${!ALL[@]}"; do
196-
(( i % SHARD_COUNT == SHARD )) && EXAMPLES+=("${ALL[$i]}")
197-
done
198-
printf '%s\n' "${EXAMPLES[@]}" > /tmp/shard-examples.txt
199-
200-
# -P1: parallelism is now expressed as shard count (job/pod level),
201-
# not concurrent processes within a single pod.
202-
printf '%s\n' "${EXAMPLES[@]}" | xargs -n1 -P1 bash -c '
203-
example="$1"
204-
log="/tmp/example-logs/$example.log"
205-
(cd "$GITHUB_WORKSPACE/examples/$example" && timeout 1200 bun index.ts) >"$log" 2>&1
206-
rc=$?
207-
[[ $rc -ne 0 ]] && echo "$rc" > "/tmp/example-fails/$example"
208-
printf "[%s] %s\n" "$([ $rc -eq 0 ] && echo "OK " || echo "FAIL")" "$example"
209-
exit $rc
210-
' _ || true
211-
212-
- name: Report results
213-
if: always()
183+
- name: Run example ${{ matrix.example }}
184+
env:
185+
EXAMPLE: ${{ matrix.example }}
214186
run: |
215-
mapfile -t EXAMPLES < /tmp/shard-examples.txt
216-
mapfile -t FAILED < <(ls /tmp/example-fails/ 2>/dev/null | sort)
217-
total=${#EXAMPLES[@]}
218-
nfailed=${#FAILED[@]}
219-
220-
{
221-
echo "## Examples (shard ${SHARD}): $(( total - nfailed ))/${total} passed"
222-
echo ""
223-
echo "| Example | Result |"
224-
echo "| --- | --- |"
225-
for ex in "${EXAMPLES[@]}"; do
226-
if [[ -f "/tmp/example-fails/$ex" ]]; then
227-
echo "| \`$ex\` | ❌ |"
228-
else
229-
echo "| \`$ex\` | ✅ |"
230-
fi
231-
done
232-
} >> "$GITHUB_STEP_SUMMARY"
233-
234-
# Example failures are advisory: flaky external deps (LLM APIs,
235-
# sandbox quota/429s, play.min.io bucket purges) shouldn't red the
236-
# run. Surface them as warnings + full logs, but do not exit non-zero.
237-
if [[ $nfailed -gt 0 ]]; then
238-
echo "::warning::${nfailed} of ${total} example(s) failed in shard ${SHARD}: ${FAILED[*]}"
239-
for ex in "${FAILED[@]}"; do
240-
echo "::group::FAILED: $ex"
241-
cat "/tmp/example-logs/$ex.log" || true
242-
echo "::endgroup::"
243-
done
187+
set +e
188+
(cd "examples/$EXAMPLE" && timeout 1200 bun index.ts)
189+
rc=$?
190+
set -e
191+
if [ "$rc" -eq 0 ]; then
192+
echo "## ✅ $EXAMPLE passed" >> "$GITHUB_STEP_SUMMARY"
193+
else
194+
echo "## ❌ $EXAMPLE failed (rc=$rc)" >> "$GITHUB_STEP_SUMMARY"
195+
# Example failures are advisory: flaky external deps (LLM APIs,
196+
# sandbox quota/429s, play.min.io bucket purges) shouldn't red the
197+
# run. Surface as a warning (full log is already in the job output),
198+
# but exit 0 so the matrix stays green.
199+
echo "::warning::example $EXAMPLE failed (rc=$rc)"
244200
fi

0 commit comments

Comments
 (0)