CI Recipes (per ecosystem & per CI)
Copy-paste recipes to push results to UniTrack from any CI. Two steps everywhere:
-
Make your tests emit JUnit XML + a coverage report (most tools need a non-default reporter — see the footguns below).
-
Add one upload step (the
unitrack-cliengine, run as a GitHub Action, a Docker image, orjava -jar).
The uploader auto-detects project/branch/commit/build-url/PR/run-key from the CI environment, and defaults to conventional report globs — so most recipes are one line.
1. Step 1 — emit the reports (per ecosystem)
The silent footgun in every stack: the test runner does not write JUnit XML or coverage XML by default. Add the reporter, then point the uploader at its output.
| Ecosystem | JUnit XML | Coverage (XML/LCOV) |
|---|---|---|
JVM (Maven/Gradle) |
Surefire writes |
JaCoCo XML is opt-in: Maven |
Python |
|
|
JS/TS |
Add |
Istanbul/ |
.NET |
|
Coverlet → |
Go |
Neither output is native: |
|
2. Step 2 — upload (per CI)
2.1. GitHub Actions
- uses: alexmond/unitrack/action@v0
with:
url: ${{ vars.UNITRACK_URL }}
token: ${{ secrets.UNITRACK_TOKEN }}
# junit/jacoco default to conventional globs; override with `junit:` / `jacoco:` if needed
# split-by-module: "true" # multi-module: each module becomes its own coverage component
project/branch/commit/buildUrl/build number/PR number/run-key are auto-detected from GITHUB_*.
For a multi-module build, set split-by-module: "true". Each module (the directory before
/target/) is uploaded as its own coverage flag/component — so the project shows per-module
tests, coverage and quality gates under "Coverage by flag" — plus a merged rollup that stays the
project’s headline. Without it, every module merges into one flat run.
|
2.2. GitLab CI
unitrack:
stage: .post
image: ghcr.io/alexmond/unitrack-uploader:0
script:
- upload --junit "**/junit.xml" --jacoco "**/coverage.xml"
variables:
UNITRACK_URL: $UNITRACK_URL
UNITRACK_TOKEN: $UNITRACK_TOKEN
Branch/commit/MR-IID/pipeline-URL are auto-detected from CI_*.
2.3. Jenkins (declarative)
stage('UniTrack') {
steps {
sh '''
docker run --rm -v "$PWD:/work" -w /work \
-e UNITRACK_URL -e UNITRACK_TOKEN \
ghcr.io/alexmond/unitrack-uploader:0 \
upload --junit "**/surefire-reports/*.xml" --jacoco "**/jacoco.xml"
'''
}
}
2.4. CircleCI
- run:
name: Upload to UniTrack
command: |
docker run --rm -v "$PWD:/work" -w /work \
-e UNITRACK_URL -e UNITRACK_TOKEN \
ghcr.io/alexmond/unitrack-uploader:0 \
upload --junit "**/test-results/**/*.xml"
Anywhere a JDK is already present, the same command is java -jar unitrack-cli.jar upload ….
3. The quality gate
Fail the build on a red gate with a second step (exit 1 = gate failed):
# any CI, after the upload
docker run --rm -e UNITRACK_URL -e UNITRACK_TOKEN \
ghcr.io/alexmond/unitrack-uploader:0 \
gate --project myapp --commit "$GIT_SHA"
The GitHub Action exposes the same via a gate: true input. See Quality Gate.
4. Behind Cloudflare Access (or any proxy/WAF)
When the server is exposed publicly behind a blanket gate, keep the gate on the UI and let CI
reach the API with a machine credential — the two are already separate paths (/api/** vs the
dashboard at /), so a per-path policy covers it without moving anything.
With Cloudflare Access, create two Access applications on the host:
-
unitrack.example.org/api/*→ a Service Token policy (machines). -
unitrack.example.org(everything else) → interactive login (humans).
The uploader then sends the service-token headers via the action’s headers: input (or the CLI’s
repeatable --header / -H):
- uses: alexmond/unitrack/action@v0
with:
url: ${{ vars.UNITRACK_URL }}
token: ${{ secrets.UNITRACK_TOKEN }} # app-level ingest token (authn at UniTrack)
headers: | # Cloudflare Access service token (authn at the edge)
CF-Access-Client-Id: ${{ secrets.CF_ACCESS_CLIENT_ID }}
CF-Access-Client-Secret: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
Run the server in closed mode so it enforces auth too (defense in depth): set
unitrack.security.open-mode=false and unitrack.security.require-ingest-token=true. See
Configuration and Accounts & API Tokens.
5. Fork-PR behaviour
On most CIs, secrets are not exposed to workflows triggered by forked pull requests, so the
upload step has no UNITRACK_TOKEN and is skipped/fails. Options: guard the step to run only on
same-repo events, or (open-mode servers) allow tokenless ingest. Don’t fail the build on a
missing token for forks — wrap the upload in a conditional or use the uploader’s --soft-fail.
See Accounts & API Tokens for token scopes.