vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Let a job declare the secrets it reads
4ac42680eca3181efc70099f65d6e2c9f45e9dd2 · Keenan Nemetz · 7d ago · parent 0c40a1d3
Let a job declare the secrets it reads
A job now names the repository secrets it needs, and the instance delivers
those values when the job is leased. The workflow carries names only, so the
queue row, the metadata branch and the run record never hold a value: the
record names the version of each value the job ran with, which is what makes a
result say which one it used.
jobs:
deploy:
image: alpine:3.22
secrets: [DEPLOY_KEY]
A name the repository has no value for is not a failure. The variable is left
unset rather than empty, the step's shell can tell the difference, and the
runner writes one line to the log saying which value was missing. That keeps
an optional secret optional and leaves "required" with the step, where someone
can write the message: ${DEPLOY_KEY:?DEPLOY_KEY is not set}.
The value reaches the step through a file the runner writes into the run
directory it already mounts at /run/vole for an event payload, and each step is
prefixed with `. /run/vole/secrets.env`. That is deliberately not an env-file
argument to the runtime, which cannot carry a line break, and not --env, which
puts the value in the process's argument list where every process on the host
can read it. A local `vole workflow run` writes the same file, reading the
values with the developer's own credential, and says which ones it could not
read.
The name rule and the 128 KiB limit moved to ci, next to the environment rules
they mirror, so the store refuses a name no workflow could declare and the
workflow refuses a name no process environment could carry.
Slice 2 of #154.
docs/reference/workflow-reference.md +56−2
| @@ -23,6 +23,8 @@ Unknown keys are rejected. A workflow is validated when it is pushed and again b | |||
| 23 | 23 | | Maximum attempt log | 32 MiB (then truncated with a note) | | |
| 24 | 24 | | Maximum `runs-on` labels per job | 8 | | |
| 25 | 25 | | Maximum step command size | 64 KiB | | |
| 26 | | Maximum secret value | 128 KiB (`NAME=value`, the kernel's environment limit) | | ||
| 27 | | Maximum secret name | 64 bytes | | ||
| 26 | 28 | | Maximum display name | 80 bytes | | |
| 27 | 29 | | Maximum description | 500 bytes | | |
| 28 | 30 | | Maximum path filters | 20 per list | | |
| @@ -408,6 +410,7 @@ letter. At least one job is required. | |||
| 408 | 410 | | `image` | string | one of `image` / `image-file` | — | Registry image the job runs in. | | |
| 409 | 411 | | `image-file` | string | one of `image` / `image-file` | — | Name of a definition under `.vole/images/`. | | |
| 410 | 412 | | `runs-on` | string or list of strings | no | any runner | Runner labels the job requires. | | |
| 413 | | `secrets` | string or list of strings | no | none | Repository secrets the job reads, by name. Each name is also the variable a step finds it in. | | ||
| 411 | 414 | | `env` | mapping | no | none | Environment every step of the job runs with. | | |
| 412 | 415 | | `steps` | list | yes | — | Commands the job runs. At least one is required. | | |
| 413 | 416 | | `artifacts` | list | no | none | Files the job publishes. | | |
| @@ -540,6 +543,52 @@ it. The job cache is the one that matters today: the runner mounts a directory a | |||
| 540 | 543 | `npm_config_cache`) there is what makes the cache shared. See | |
| 541 | 544 | [Runners § Caching](../runner.md#caching). | |
| 542 | 545 | ||
| 546 | ### `secrets` | ||
| 547 | |||
| 548 | The repository secrets this job reads. Each entry is a name, never a value: the | ||
| 549 | instance supplies the values when the job is leased, and the name is also the | ||
| 550 | environment variable a step finds it in. One name may be written on its own: | ||
| 551 | |||
| 552 | ```yaml | ||
| 553 | jobs: | ||
| 554 | deploy: | ||
| 555 | image: alpine:3.22 | ||
| 556 | secrets: DEPLOY_KEY | ||
| 557 | steps: | ||
| 558 | - run: ./deploy.sh | ||
| 559 | ``` | ||
| 560 | |||
| 561 | or as a list: | ||
| 562 | |||
| 563 | ```yaml | ||
| 564 | secrets: [DEPLOY_KEY, REGISTRY_TOKEN] | ||
| 565 | ``` | ||
| 566 | |||
| 567 | Names are upper-case letters, digits and `_`, at most 64 bytes, starting with a | ||
| 568 | letter. A name that repeats another entry is refused, and so is one the runner sets | ||
| 569 | itself (`CI`, `VOLE_CI`, `HOME`, `TMPDIR`, `GIT_TERMINAL_PROMPT`, and the | ||
| 570 | `VOLE_EVENT*` variables). | ||
| 571 | |||
| 572 | A declared name the repository has no value for is not an error at push time and | ||
| 573 | does not fail the job. The variable is left **unset** — a step can tell that apart | ||
| 574 | from empty — and the runner writes one line to the log: | ||
| 575 | |||
| 576 | ``` | ||
| 577 | vole: DEPLOY_TOKEN is not set for this repository, so this job runs without it. | ||
| 578 | ``` | ||
| 579 | |||
| 580 | A step that requires one says so with the shell's own check: | ||
| 581 | `: "${DEPLOY_TOKEN:?DEPLOY_TOKEN is not set}"`. | ||
| 582 | |||
| 583 | A value is at most 128 KiB, the kernel's limit for one `NAME=value` string in a | ||
| 584 | process environment, and it is not part of the workflow, of any container argument, | ||
| 585 | or of any run record. A record names the version of each value the job ran with. | ||
| 586 | |||
| 587 | The values reach the step through a file the runner writes into the run directory | ||
| 588 | mounted at `/run/vole`, and the runner sources it before each step. There is no | ||
| 589 | expressions layer: a value is a variable, and a step that builds a name at runtime | ||
| 590 | reads the file itself. | ||
| 591 | |||
| 543 | 592 | ### `steps` | |
| 544 | 593 | ||
| 545 | 594 | A non-empty list of shell steps, at most 100. Steps run in order. A step that exits | |
| @@ -634,7 +683,10 @@ It does not support: | |||
| 634 | 683 | ||
| 635 | 684 | - a job graph: there is no `needs`, no job outputs, and no shared artifacts between jobs | |
| 636 | 685 | - a build matrix or per-step conditions (`strategy`, `if`, `continue-on-error`) | |
| 637 | - secrets, secret masking, or an audit trail for secret use | ||
| 686 | - step-scoped secrets, expressions for building a secret name, or a value written | ||
| 687 | in the workflow file; secrets are job-scoped and the instance holds the values | ||
| 688 | - masking of a value in a step's output: a step that prints a secret writes it to | ||
| 689 | the log, so a workflow is responsible for what its steps print | ||
| 638 | 690 | - expressions or contexts (`${{ }}`); nothing outside the shell is interpolated | |
| 639 | 691 | - service containers, cache keys, or artifact downloads between jobs | |
| 640 | 692 | - job-level timeouts, concurrency groups, or cancel-in-progress | |
| @@ -648,7 +700,9 @@ It does not support: | |||
| 648 | 700 | Combined results for a commit under review are still produced automatically by the | |
| 649 | 701 | server; `on.change-request` is the event trigger, not the combined-result scheduler. | |
| 650 | 702 | A local `vole workflow run` ignores triggers and `runs-on`, publishes no artifacts, | |
| 651 | deploys no site, and has no event payload. | ||
| 703 | deploys no site, and has no event payload. It reads the secrets its jobs declare from | ||
| 704 | the instance the checkout's remote names, and leaves the variables unset — with a | ||
| 705 | note — where it cannot. | ||
| 652 | 706 | ||
| 653 | 707 | ## Validation and pinning | |
| 654 | 708 | ||