flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/crates/runner/src/actions/paths.rs

87 lines3,069 bytesCodeBlame

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 incidents1//! 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
7use std::path::PathBuf;
8
9use serde_json::Value;
10
11/// GitHub's runner layout, as g1t's sandbox has it.
12pub(crate) const HOME_RUNNER: &str = "/home/runner";
13
14/// The folder standing in for `/home/runner`, if the job runs elsewhere.
15pub(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.
20pub(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.
29pub(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`.
35fn 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.
44pub(crate) fn relocate(value: &mut Value) {
45 let Some(root) = root() else { return };
46 relocate_to(value, &shown(&root));
47}
48
49fn 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.
59pub(crate) const PATH_SEPARATOR: &str = if cfg!(windows) { ";" } else { ":" };
60
61#[cfg(test)]
62mod 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}