- Python 100%
| .orchestrator | ||
| src/wordtools | ||
| tests | ||
| tools | ||
| .DS_Store | ||
| .gitignore | ||
| after | ||
| before_test | ||
| merge_flag_3 | ||
| README.md | ||
| sample.txt | ||
| test | ||
wordtools
A tiny text-analysis library, and the reason it exists: a pipeline that guards
the main branch.
The library is deliberately small — split text into words, count them, print a
report. Standard library only, no pip install anywhere. The interesting part
is .orchestrator/actions.yml.
The rule the pipeline implements
push to any branch except main
-> run every check and every test
-> if all of them pass, merge that branch into main
Push to main directly and the pipeline runs but every job exits immediately:
main is the merged result, so there is nothing to check.
The shape
check test merge
┌──────────┐ ┌─────────────┐ ┌───────────────┐
│ compile │ │ test-words │ │ merge-to-main │
│ style │ ──▶ │ test-stats │ ──▶ │ │
└──────────┘ │ test-report │ └───────────────┘
│ test-switch │
└─────────────┘
Seven jobs, and between them the two things worth understanding:
Parallel. compile and style declare no needs, so the scheduler
releases both at once. One stage down, the four test jobs all become runnable
at the same moment and run side by side — on different machines, if more than
one worker is online. One test job per module, so a red job names the module
that broke instead of just saying "the tests failed".
Chain. The four test jobs needs: [compile], so none of them starts until
compile has passed. merge-to-main needs all five jobs before it, so it is
reached only when every one of them is green.
That chain is what makes this a gate rather than a suggestion. When a job
fails, the orchestrator marks everything downstream of it
skipped: dependency '<job>' failed and never runs it. A broken branch cannot
reach the merge job at all.
How the branch rule is written
This orchestrator has no only: / except: filter, so each job decides for
itself, on its first line:
if [ "$REPO_BRANCH" = "main" ]; then echo "on main — nothing to do"; exit 0; fi
REPO_BRANCH, REPO_URL and COMMIT_SHA are injected into every job by the
coordinator, so nothing here hardcodes the repo or the branch.
Setting up the merge
tools/merge_to_main.py opens a pull request in Forgejo and merges it. It
needs a token with write access, and the token must be visible to the job.
The executor passes its own environment through to every job it runs, so set
it there — in the executor service in docker-compose.yml:
environment:
FORGEJO_TOKEN: <a token with write access to the repo>
Do not put the token in the env: block of actions.yml — that block
lives in the repo in plain text.
Create the token in Forgejo under Settings → Applications, with
write:repository scope.
One more setting: the webhook in Forgejo must be a push webhook pointing at
POST http://<coordinator>/api/webhooks/forgejo with content type JSON. Leave
the other event types off — a pull-request payload has no branch ref, and the
run would be labelled against the wrong branch.
Trying it
git switch -c try-something
echo "just testing" >> sample.txt
git commit -am "test the pipeline"
git push -u origin try-something
Watch the run appear in the dashboard. When the four jobs go green,
merge-to-main opens a pull request and merges it, and try-something is now
part of main.
Showing the failing path
tests/test_switch.py exists only for this. It has one flag in it:
FAIL_ON_PURPOSE = False
Set it to True, commit, push. test-switch goes red, merge-to-main is
marked skipped: dependency 'test-switch' failed, and nothing reaches main.
Set it back to False, push again on the same branch, and the run goes green
and merges. Nothing else in the library touches that flag, so flipping it is
always safe.
You can also break something real — change a number in tests/test_stats.py,
or push a line longer than 88 characters to fail style.
Running it locally
python3 -m compileall -q src tests tools # what `compile` does
python3 tools/style_check.py # what `style` does
PYTHONPATH=src python3 -m unittest discover -s tests -t . -v
PYTHONPATH=src python3 -m wordtools sample.txt # the library itself
Layout
src/wordtools/ words, stats, report, cli — the library
tests/ one test file per module
tools/ style_check.py, merge_to_main.py
.orchestrator/ actions.yml — the pipeline
sample.txt something for the CLI to read