A claim carries the hub's variables for the job c58efc6a74db
The hub holds variables per site, organisation and repository (oxygenhub#102); they come with the claim as env and join the job's environment under FLOW_* and the pipeline's own env, so they are part of the receipt. flow run --var NAME=VALUE does the same locally. Secrets are not variables: spec §9 mounts them. refs oxygenhub#102
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-10-04 15:52 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
c58efc6a74dbe2b12cb67d5b277a357fb1e1054e40a6fa99a5ea02ff2d37c6a6- Depends on
- 02b127d2c721 a72abeeedd78 c3db750ed96e c4285535b03a e0918b887850 f98a8f7881e0
- Depended on by
- ea56c56eaf90 720eb8191059 e2c125610f43
- In bookmarks
- main newer-pipeline push-uses-login
- In tags
- v0.2.0 v0.3.0 v0.3.1 v0.4.0 v0.5.0 v0.5.1 v0.5.2 v0.5.3
- Edits
- 15 atoms, 0 tree operations, 5 files
Browse the tree at this change
modified CHANGELOG.md +2 −0
| … | 3 unchanged lines |
||
| 4 | 4 | ||
| 5 | 5 | ## Unreleased |
|
| 6 | 6 | ||
| 7 | + | - **Variables from the hub (oxygenhub#102).** A claim carries `env`, the variables the hub holds for the repository; they join the job's environment under `FLOW_*` and the pipeline's own `env`. `flow run --var NAME=VALUE` does the same locally. |
|
| 8 | + | ||
| 7 | 9 | - **A job says how long it may run (#23).** `timeout "90m"` on a job, or on a sandbox for its jobs (hours, minutes, seconds; a minute to a day). The hub caps it at the site's maximum. |
|
| 8 | 10 | ||
| 9 | 11 | - **A pinned file is re-read from the disk before its hash is called wrong (#22).** |
|
| … | 8 unchanged lines |
modified crates/flow-core/src/api.rs +12 −0
| … | 133 unchanged lines |
||
| 134 | 134 | /// The run's number within its repository. |
|
| 135 | 135 | #[serde(default)] |
|
| 136 | 136 | pub run_number: i64, |
|
| 137 | + | /// Variables the hub holds for the repository (its site's, its |
|
| 138 | + | /// organisation's and its own, inner overriding outer; oxygenhub#102). |
|
| 139 | + | /// Set under `FLOW_*` and under the pipeline's own `env`, so they are |
|
| 140 | + | /// part of the job's environment and of its receipt. Never secrets: |
|
| 141 | + | /// those are mounted, not put in the environment (spec §9). |
|
| 142 | + | #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")] |
|
| 143 | + | pub env: std::collections::BTreeMap<String, String>, |
|
| 137 | 144 | } |
|
| 138 | 145 | ||
| 139 | 146 | impl Claim { |
|
| … | 16 unchanged lines |
||
| 156 | 163 | if self.run_number > 0 { |
|
| 157 | 164 | v.push(("FLOW_RUN".into(), self.run_number.to_string())); |
|
| 158 | 165 | } |
|
| 166 | + | for (k, val) in &self.env { |
|
| 167 | + | if !v.iter().any(|(n, _)| n == k) { |
|
| 168 | + | v.push((k.clone(), val.clone())); |
|
| 169 | + | } |
|
| 170 | + | } |
|
| 159 | 171 | v |
|
| 160 | 172 | } |
|
| 161 | 173 | } |
|
| … | 36 unchanged lines |
modified crates/flow-core/src/tests.rs +4 −2
| … | 286 unchanged lines |
||
| 287 | 287 | state: "cc".into(), |
|
| 288 | 288 | bookmark: "feature/x".into(), |
|
| 289 | 289 | run_number: 3, |
|
| 290 | + | env: [("CC".to_string(), "clang".to_string()), ("FLOW_REPO".to_string(), "spoofed".to_string())].into_iter().collect(), |
|
| 290 | 291 | }; |
|
| 291 | 292 | let env = job.with_flow_env(&claim.flow_env()).env; |
|
| 292 | - | assert_eq!(env["FLOW_REPO"], "andy/demo"); |
|
| 293 | + | assert_eq!(env["CC"], "clang", "the hub's variables reach the job"); |
|
| 294 | + | assert_eq!(env["FLOW_REPO"], "andy/demo", "a hub variable cannot pose as a FLOW_ fact"); |
|
| 293 | 295 | assert_eq!(env["FLOW_HEADS"], "aa+bb"); |
|
| 294 | 296 | assert_eq!(env["FLOW_STATE"], "cc"); |
|
| 295 | 297 | assert_eq!(env["FLOW_RUN"], "3", "the run's number in its repository, not the hub's id"); |
|
| 296 | 298 | assert_eq!(env["FLOW_JOB"], "build"); |
|
| 297 | 299 | assert_eq!(env["FLOW_BOOKMARK"], "pinned-by-the-pipeline", "the pipeline's own value wins"); |
|
| 298 | 300 | // Unknown values (an older hub) are left out, not empty. |
|
| 299 | - | let old = crate::api::Claim { state: String::new(), bookmark: String::new(), run_number: 0, ..claim }; |
|
| 301 | + | let old = crate::api::Claim { state: String::new(), bookmark: String::new(), run_number: 0, env: Default::default(), ..claim }; |
|
| 300 | 302 | let names: Vec<String> = old.flow_env().into_iter().map(|(k, _)| k).collect(); |
|
| 301 | 303 | assert_eq!(names, ["FLOW_REPO", "FLOW_HEADS", "FLOW_JOB"]); |
|
| 302 | 304 | } |
|
| … | 113 unchanged lines |
modified crates/flow/src/main.rs +19 −4
| … | 72 unchanged lines |
||
| 73 | 73 | /// Minutes before a job is stopped |
|
| 74 | 74 | #[arg(long, default_value_t = 60)] |
|
| 75 | 75 | timeout: u64, |
|
| 76 | + | /// A variable for every job, as the hub's settings would give it; |
|
| 77 | + | /// the pipeline's own `env` wins |
|
| 78 | + | #[arg(long = "var", value_name = "NAME=VALUE")] |
|
| 79 | + | vars: Vec<String>, |
|
| 76 | 80 | }, |
|
| 77 | 81 | /// Claim and run jobs for the hub in flow.toml's [runner] (or the |
|
| 78 | 82 | /// runner.toml `flow runner register` wrote) |
|
| … | 63 unchanged lines |
||
| 142 | 146 | } |
|
| 143 | 147 | } |
|
| 144 | 148 | } |
|
| 145 | - | Cmd::Run { jobs, dir, timeout } => { |
|
| 149 | + | Cmd::Run { jobs, dir, timeout, vars } => { |
|
| 146 | 150 | let home = Home::locate(cli.home.as_deref())?; |
|
| 147 | - | run_local(&home, &dir, &jobs, Duration::from_secs(timeout * 60)) |
|
| 151 | + | let vars = vars |
|
| 152 | + | .iter() |
|
| 153 | + | .map(|e| { |
|
| 154 | + | e.split_once('=') |
|
| 155 | + | .map(|(k, v)| (k.to_string(), v.to_string())) |
|
| 156 | + | .ok_or_else(|| anyhow::anyhow!("--var {e:?}: expected NAME=VALUE")) |
|
| 157 | + | }) |
|
| 158 | + | .collect::<Result<Vec<_>>>()?; |
|
| 159 | + | run_local(&home, &dir, &jobs, Duration::from_secs(timeout * 60), &vars) |
|
| 148 | 160 | } |
|
| 149 | 161 | Cmd::Runner { cmd: Some(RunnerCmd::Register { hub, token, name, ca, label }) } => { |
|
| 150 | 162 | let home = Home::locate(cli.home.as_deref())?; |
|
| … | 123 unchanged lines |
||
| 274 | 286 | Ok(()) |
|
| 275 | 287 | } |
|
| 276 | 288 | ||
| 277 | - | fn run_local(home: &Home, dir: &Path, only: &[String], timeout: Duration) -> Result<()> { |
|
| 289 | + | fn run_local(home: &Home, dir: &Path, only: &[String], timeout: Duration, vars: &[(String, String)]) -> Result<()> { |
|
| 278 | 290 | let p = load(&dir.join(PIPELINE_PATH))?; |
|
| 279 | 291 | // The named jobs and everything they depend on. |
|
| 280 | 292 | let mut wanted: BTreeSet<String> = only.iter().cloned().collect(); |
|
| … | 35 unchanged lines |
||
| 316 | 328 | std::fs::create_dir_all(&js)?; |
|
| 317 | 329 | // Each job starts from the same files: jobs do not share writes. |
|
| 318 | 330 | workspace::image(&scratch.join("tree"), &js.join("workspace.ext4"))?; |
|
| 319 | - | let job = &job.in_sandbox(sb).with_flow_env(&[("FLOW_JOB".into(), job.name.clone()), ("FLOW_RUN".into(), "local".into())]); |
|
| 331 | + | let job = &job |
|
| 332 | + | .in_sandbox(sb) |
|
| 333 | + | .with_flow_env(&[("FLOW_JOB".into(), job.name.clone()), ("FLOW_RUN".into(), "local".into())]) |
|
| 334 | + | .with_flow_env(vars); |
|
| 320 | 335 | let out = microvm::run(home, sb, job, &js.join("workspace.ext4"), &js, timeout, &mut |r: Record| print_record(&r))?; |
|
| 321 | 336 | let _ = std::fs::remove_dir_all(&js); |
|
| 322 | 337 | match out.exit_code { |
|
| … | 42 unchanged lines |
modified docs/m0.md +5 −2
| … | 119 unchanged lines |
||
| 120 | 120 | is told what it builds: `FLOW_REPO`, `FLOW_STATE` (the hub's state hash), |
|
| 121 | 121 | `FLOW_HEADS` (full hashes joined with `+`), `FLOW_BOOKMARK`, `FLOW_RUN` |
|
| 122 | 122 | (the run's number in its repository) and `FLOW_JOB`; `flow run` sets |
|
| 123 | - | `FLOW_JOB` and `FLOW_RUN=local`. A variable the pipeline sets keeps its |
|
| 124 | - | value. They are part of the job's environment, so of its receipt. |
|
| 123 | + | `FLOW_JOB` and `FLOW_RUN=local`. After them come the **variables** the |
|
| 124 | + | hub holds for the repository (site, organisation, repository; the |
|
| 125 | + | claim's `env`; `flow run --var NAME=VALUE` locally), never secrets. A |
|
| 126 | + | variable the pipeline sets keeps its value. They are part of the job's |
|
| 127 | + | environment, so of its receipt. |
|
| 125 | 128 | 5. `flow-init` frames the child's output on the serial console as |
|
| 126 | 129 | `\x1eO <line>` (stdout) and `\x1eE <line>` (stderr), then |
|
| 127 | 130 | `\x1eX <code>`, and powers off. The kernel runs `quiet` so the host sees |
|
| … | 88 unchanged lines |