g1t/crates/runner/src/actions/paths.rs
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Fast pages, required checks on the branch, self-hosted runners, honest incidents | 1 | //! Where a job's files go. In g1t's sandbox (and in a container a |
| 2 | //! self-hosted runner starts) that is GitHub's layout under `/home/runner`. | |
| 3 | //! A self-hosted runner running a job directly on its machine | |
| 4 | //! (`g1t-runner run --no-docker`) sets `G1T_RUNNER_ROOT` to a folder of its | |
| 5 | //! own, and every `/home/runner` path, the spec's included, moves there. | |
| 6 | ||
| 7 | use std::path::PathBuf; | |
| 8 | ||
| 9 | use serde_json::Value; | |
| 10 | ||
| 11 | /// GitHub's runner layout, as g1t's sandbox has it. | |
| 12 | pub(crate) const HOME_RUNNER: &str = "/home/runner"; | |
| 13 | ||
| 14 | /// The folder standing in for `/home/runner`, if the job runs elsewhere. | |
| 15 | pub(crate) fn root() -> Option<PathBuf> { | |
| 16 | std::env::var_os("G1T_RUNNER_ROOT").filter(|root| !root.is_empty()).map(PathBuf::from) | |
| 17 | } | |
| 18 | ||
| 19 | /// `path` (under `/home/runner`) where this job keeps it. | |
| 20 | pub(crate) fn under_home(path: &str) -> PathBuf { | |
| 21 | match (root(), path.strip_prefix(HOME_RUNNER)) { | |
| 22 | (Some(root), Some(rest)) => root.join(rest.trim_start_matches('/')), | |
| 23 | _ => PathBuf::from(path), | |
| 24 | } | |
| 25 | } | |
| 26 | ||
| 27 | /// A path as the job's processes see it: with forward slashes, which every | |
| 28 | /// shell takes, Windows' included. | |
| 29 | pub(crate) fn shown(path: &std::path::Path) -> String { | |
| 30 | let text = path.display().to_string(); | |
| 31 | if cfg!(windows) { text.replace('\\', "/") } else { text } | |
| 32 | } | |
| 33 | ||
| 34 | /// `text` with `/home/runner` moved to `root`. | |
| 35 | fn moved(text: &str, root: &str) -> String { | |
| 36 | match text.strip_prefix(HOME_RUNNER) { | |
| 37 | Some(rest) if rest.is_empty() || rest.starts_with('/') => format!("{root}{rest}"), | |
| 38 | _ => text.to_owned(), | |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | /// Moves every `/home/runner` path in a spec's values (its variables, | |
| 43 | /// `github` and `runner` contexts) to where this job keeps its files. | |
| 44 | pub(crate) fn relocate(value: &mut Value) { | |
| 45 | let Some(root) = root() else { return }; | |
| 46 | relocate_to(value, &shown(&root)); | |
| 47 | } | |
| 48 | ||
| 49 | fn relocate_to(value: &mut Value, root: &str) { | |
| 50 | match value { | |
| 51 | Value::String(text) => *text = moved(text, root), | |
| 52 | Value::Array(items) => items.iter_mut().for_each(|item| relocate_to(item, root)), | |
| 53 | Value::Object(fields) => fields.values_mut().for_each(|item| relocate_to(item, root)), | |
| 54 | _ => {} | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | /// How `PATH` separates its entries here. | |
| 59 | pub(crate) const PATH_SEPARATOR: &str = if cfg!(windows) { ";" } else { ":" }; | |
| 60 | ||
| 61 | #[cfg(test)] | |
| 62 | mod tests { | |
| 63 | use super::*; | |
| 64 | use serde_json::json; | |
| 65 | ||
| 66 | #[test] | |
| 67 | fn home_runner_paths_move_and_nothing_else_does() { | |
| 68 | let mut spec = json!({ | |
| 69 | "GITHUB_WORKSPACE": "/home/runner/work/repo", | |
| 70 | "RUNNER_TEMP": "/home/runner/_temp", | |
| 71 | "HOME": "/home/runnerish", | |
| 72 | "list": ["/home/runner", "/etc/hosts"], | |
| 73 | "n": 3, | |
| 74 | }); | |
| 75 | relocate_to(&mut spec, "/srv/jobs/job_1"); | |
| 76 | assert_eq!( | |
| 77 | spec, | |
| 78 | json!({ | |
| 79 | "GITHUB_WORKSPACE": "/srv/jobs/job_1/work/repo", | |
| 80 | "RUNNER_TEMP": "/srv/jobs/job_1/_temp", | |
| 81 | "HOME": "/home/runnerish", | |
| 82 | "list": ["/srv/jobs/job_1", "/etc/hosts"], | |
| 83 | "n": 3, | |
| 84 | }) | |
| 85 | ); | |
| 86 | } | |
| 87 | } |